Guides
Detection guide

Rage Clicks: How to Detect Frustrated Users From Click Events

By Sazabi Team

Last verified on Nov 8, 2026.

A rage click is what a frustrated user does when a control seems broken: hammer it. The page looks fine to your monitoring, because each click is a normal event. The pattern across clicks is the signal, and it is easy to compute once you have raw click events with timestamps and positions.

Rage clicks differ from dead clicks in what they measure. A dead click is one click with no effect. A rage click is the behavior that follows it, so the two often appear together, but you can have a rage click on a control that works and is merely slow.

How other tools define it

Two public definitions help you choose parameters. PostHog captures a rage click when a user clicks three times in rapid succession in roughly the same spot, with each click within 30 pixels and 1 second of the previous one. Sentry describes rage clicks as a series of consecutive clicks on the same unresponsive element, registered after three or more clicks within a 7 second window when nothing on the page changes. It also lets you ignore selectors that cause false reports.

You do not need either tool. The definition is three numbers: click count, time gap and distance.

What the evidence looks like

In raw events, a burst is a run of click records from one session on one target with short gaps. This example shows one burst:

ts=14:20:03.112 session=s_41 target=button#save x=640 y=412
ts=14:20:03.540 session=s_41 target=button#save x=641 y=413
ts=14:20:03.901 session=s_41 target=button#save x=640 y=412
ts=14:20:04.350 session=s_41 target=button#save x=639 y=411

The same session may also trigger a server-side signature: four identical POST requests within five seconds from one user.

How to detect it

  1. Capture every click with a session id, timestamp, target selector and coordinates. The browser click event also exposes a detail property with the current click count, which helps for quick double-click filtering.
  2. Define a burst: at least 3 clicks on the same target, each within 1 second of the previous, inside a short distance.
  3. Run the burst query over events.
WITH gaps AS (
  SELECT session_id, target, ts,
         ts - lag(ts) OVER (PARTITION BY session_id, target ORDER BY ts) AS gap
  FROM click_events
  WHERE ts > now() - interval '1 day'
), runs AS (
  SELECT *,
         sum(CASE WHEN gap IS NULL OR gap > interval '1 second' THEN 1 ELSE 0 END)
           OVER (PARTITION BY session_id, target ORDER BY ts) AS run_id
  FROM gaps
)
SELECT target,
       count(DISTINCT (session_id, run_id)) AS bursts,
       count(DISTINCT session_id) AS sessions
FROM (
  SELECT session_id, target, run_id, count(*) AS clicks
  FROM runs
  GROUP BY session_id, target, run_id
  HAVING count(*) >= 3
) b
GROUP BY target
ORDER BY bursts DESC
LIMIT 20;

To compute it in the browser instead, keep a run per element, using the same rule of at most 1 second between consecutive clicks:

const runs = new WeakMap()
document.addEventListener('click', (e) => {
  const el = e.target.closest('button, a, [role="button"], [data-testid]') ?? e.target
  const now = performance.now()
  const run = runs.get(el)
  const count = run && now - run.last <= 1000 ? run.count + 1 : 1
  runs.set(el, { last: now, count })
  if (count === 3) track('rage_click', { target: el.dataset.testid || el.id || el.tagName })
})

Count distinct sessions per target and compare against that target's total clicks, so a control that is popular is not mistaken for a broken one.

For the server side, look for repeat submissions: the same user and route inside a short window.

SELECT user_id, route, count(*) AS submits, min(ts) AS first, max(ts) AS last
FROM request_log
WHERE method = 'POST' AND ts > now() - interval '1 hour'
GROUP BY user_id, route, date_trunc('minute', ts)
HAVING count(*) >= 3;

Thresholds and pitfalls

Rank by sessions affected, not raw bursts. One user can produce 40 bursts. A control with 15 distinct sessions this week is a real finding.

Filter the controls that invite repeats: quantity steppers, carousel arrows, pagination, games and text selection. Add a data attribute such as data-repeat-ok and skip it in both the script and the query. Ignore touch scroll gestures, and treat a double-click as one action. Then check whether the clicks produced a result. A burst on a button that changed the screen is impatience. A burst with no change is a defect.

Also watch slow responses. If the click triggers a 4 second request with no loading state, users will click again, and the fix is feedback, not logic. Compare burst rates for the same control after each release to catch regressions early.

What to do when it fires

Watch a few sessions with the burst. Look at what the control does: does it disable, show progress or navigate? Check for an error at the same timestamp, using the approach in client errors. If the server accepted all the repeats, you have duplicates to clean up and a missing idempotency guard. Disable the button during submission and make the endpoint safe to repeat.

The Sazabi Browser SDK records navigation, clicks, errors and network calls, which are the events the queries above read. Related guides: dead clicks and stale data when the screen kept showing old values.

Frequently Asked Questions

What is a rage click?

A rage click is a burst of rapid clicks on the same spot, usually because the control did not respond. PostHog defines it as three clicks, each within 30 pixels and 1 second of the previous one.

What is the difference between a rage click and a repeat click?

A rage click is fast and tightly clustered. A repeat click is slower, such as pressing Save five times in ten seconds. Both mean the user is unsure whether the first click worked.

Do rage clicks always mean a bug?

No. Games, quantity steppers and image carousels invite repeated clicks. Exclude those controls and judge the rest by whether the clicks produced any change.

How many clicks should count?

Three is the common minimum, as in the PostHog and Sentry definitions. Raise it to five for noisy pages and require that nothing changed on screen.

Sources

  1. PostHog captures a rage click when a user clicks three times in rapid succession in roughly the same spot, with each click within 30 pixels and 1 second of the previous one (accessed Nov 8, 2026)
  2. Rage clicks are a series of consecutive clicks on the same unresponsive element; Sentry registers one after 3 or more clicks within 7 seconds on an element that produces no DOM update or scroll, and supports ignoring selectors (accessed Nov 8, 2026)
  3. The click event and its detail property, which holds the current click count (accessed Nov 8, 2026)