Track Rage Clicks
Rage clicks — rapid, repeated clicks on the same element — signal user frustration: a button that appears clickable but is not responding, a link with a broken href, or a UI element that looks interactive but is not. Detecting them helps you find broken interactions before users complain.
What counts as a rage click
Section titled “What counts as a rage click”A rage click is typically 3 or more clicks on the same element within 500-700ms. The exact threshold depends on your use case:
- 3 clicks in 500ms: Aggressive threshold, fewer false positives from double-clickers
- 4 clicks in 700ms: More forgiving, catches slower frustrated clicking
- Any rapid clicking on error/disabled elements: Always a rage click candidate
Implementation
Section titled “Implementation”Detects 3+ rapid clicks on the same element within 500ms.
(function() { var CLICK_THRESHOLD = 3; // Minimum clicks to count as rage var TIME_WINDOW = 500; // Milliseconds var clickBuffer = {};
document.addEventListener('click', function(event) { var el = event.target;
// Walk up to find a meaningful element to track // (click on icon inside button → track the button) while (el && el !== document.body) { if ( el.tagName === 'BUTTON' || el.tagName === 'A' || el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.getAttribute('role') === 'button' || el.dataset.trackRage ) break; el = el.parentElement; }
if (!el || el === document.body) return;
// Create a stable key for this element var key = el.id || el.dataset.trackId || (el.tagName + '.' + el.className.replace(/\s+/g, '.')) || el.textContent.trim().substring(0, 30);
var now = Date.now();
if (!clickBuffer[key]) { clickBuffer[key] = { count: 0, first: now }; }
// Reset if outside time window if (now - clickBuffer[key].first > TIME_WINDOW) { clickBuffer[key] = { count: 0, first: now }; }
clickBuffer[key].count++;
// Rage click detected if (clickBuffer[key].count === CLICK_THRESHOLD) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'rage_click', element_tag: el.tagName.toLowerCase(), element_id: el.id || undefined, element_text: (el.innerText || el.value || '').trim().substring(0, 100), element_classes: el.className || undefined, click_count: clickBuffer[key].count, page_path: window.location.pathname, // Is the element disabled or non-interactive? element_disabled: el.disabled || el.getAttribute('aria-disabled') === 'true' || false, element_visible: el.offsetParent !== null });
// Reset to avoid firing repeatedly after threshold clickBuffer[key].count = 0; } }, true); // Use capture phase to catch all clicks})();-
Add the code as a Custom HTML tag in GTM
- Trigger: DOM Ready (All Pages)
-
Create a Custom Event Trigger
- Trigger type: Custom Event
- Event name:
rage_click
-
Create Data Layer Variables
DLV - element_tag→element_tagDLV - element_id→element_idDLV - element_text→element_textDLV - element_disabled→element_disabledDLV - click_count→click_count
-
Create a GA4 Event Tag
- Event name:
rage_click - Parameters:
element_tag→{{DLV - element_tag}}element_id→{{DLV - element_id}}element_text→{{DLV - element_text}}element_disabled→{{DLV - element_disabled}}click_count→{{DLV - click_count}}
- Trigger: the Custom Event trigger
- Event name:
-
Register as a Key Event in GA4 (optional)
If
element_disabled = true(user is rage-clicking a disabled button), consider markingrage_clickevents withelement_disabled = trueas Key Events — these are your most urgent UX bugs. -
Test in Preview Mode
Visit a page and click rapidly on a button 3+ times within half a second. The
rage_clickevent should appear in the Summary pane.
GA4 - rage_click
- Type
- Google Analytics: GA4 Event
- Trigger
- Custom Event - rage_click
- Variables
-
DLV - element_idDLV - element_textDLV - element_disabledDLV - click_count
Analysing rage click data in GA4
Section titled “Analysing rage click data in GA4”Create an Exploration report:
- Dimensions:
element_text,element_id,page_path - Metrics:
Event count - Filter:
Event name = rage_click - Sort by: Event count descending
The top results are your most frustrating UI elements. Investigate each one:
- Is the button hidden behind a slow network request?
- Is it disabled but not visually obvious?
- Is it overlaid by an invisible element?
- Is there a JavaScript error preventing the click from working?
Cross-reference with your error tracking if you have it: elements that generate both rage clicks AND JavaScript errors are critical fixes.
Test it
Section titled “Test it”- Open GTM Preview on any page
- Click rapidly on a button 4 times in less than 500ms
- A
rage_clickevent should appear in the Summary pane - Check
element_textandelement_idare correct - Click a disabled button rapidly — verify
element_disabledistrue
Common gotchas
Section titled “Common gotchas”False positives from text selection. Triple-clicking to select a word in a text field also fires 3 rapid clicks. Add a check: if (window.getSelection().toString().length > 0) return; to skip clicks that result in text selection.
Elements inside SPAs get recreated. If a component re-renders between clicks (common in React), clickBuffer entries may become stale because the element reference changes. The key calculation using tagName + className + text is more stable than comparing element references directly.
Capture phase is important. Using true as the third argument to addEventListener (capture phase) ensures clicks are detected even when the element’s own handler calls event.stopPropagation().