A user is stuck when they keep trying something and it keeps not working. You can see that in the browser without asking them: they click a disabled button, click the same thing three times in a second, or click a button that does nothing. This article lists the eight signals we use in Cueston's player, the exact thresholds behind each one, and how we combine them so help shows up for stuck people and stays away from everyone else.
Everything below comes from our own code, not from a survey. If you build this yourself, steal the numbers. If you buy a tool for it, use them as questions for the vendor.
The eight signals
- Disabled click: a press on a control that is disabled or has aria-disabled="true". The strongest signal there is: the person wants to do something the interface won't let them do, and usually doesn't know why.
- Disabled hover: resting the pointer on a disabled control for 1.5 seconds. Weaker, but it often comes right before a disabled click.
- Rage click: 3 or more clicks on the same element within 1 second, where at least two of them changed nothing. That last condition matters: steppers, carousels and "+" buttons react to every click, so fast clicking on them is normal use.
- Dead click: a click on a button or link that changed nothing within 0.65 seconds. No DOM change, no navigation, no focus or scroll change.
- Error after a click: within 1.5 seconds of a click, something with role="alert", aria-live="assertive" or aria-invalid="true" appears, or the browser refuses to submit a form.
- Menu toggling: opening and closing the same menu (tracked through aria-expanded) 4 or more times in 30 seconds. The person is looking for something that isn't where they expect.
- Navigation loop: coming back to the same page a third time within 2 minutes, with other pages in between.
- Idle: 30 seconds without input on a visible, focused page, after the person had been using it. Scrolling a long page with the wheel counts as input, so reading doesn't count as idle.
What is not a signal
Some behaviour looks like struggle and isn't. We ignore clicks into text fields, double and triple clicks that select text, and anything inside our own UI. Links that open a new tab or download a file are excluded from dead clicks, because by design nothing changes on the current page. And we track typing separately, so help never appears in the middle of a sentence.
Why one signal is never enough
If a single rage click opens a popup, you will interrupt people who just have a slow connection or an impatient finger. So each signal gets a weight, and help only appears when the weights add up past a threshold. Our weights, on a 0 to 100 scale:
- Disabled click: 45
- Rage click: 40
- Error after a click: 30
- Disabled hover: 30
- Navigation loop: 30
- Menu toggling: 25
- Dead click: 20
- Idle: 15
A tip attached to a specific element appears at 60. So one rage click (40) or one dead click (20) only gets logged. Clicking a disabled button twice (90) shows help. A rage click followed by another dead click on the same button (60) shows help. That is the pattern you want: a clear signal repeated, or two different signals pointing the same way.
Two more rules keep the score honest. Signals fade with a one-minute half-life, so a burst of clicks an hour ago doesn't add up with one click now. And scores are scoped: a tip on the "Invite teammates" button counts signals on that button in full, page-wide signals like idling at half weight, and signals on other elements not at all. Being lost on the page is not the same as being stuck on this button.
Different help needs different confidence
We use different thresholds for different kinds of help. A tip that a person on your team wrote for a specific button shows at 60, because it is likely to be relevant. Our AI widget's "Stuck on this?" offer waits for 70, because it has nothing prepared and has to be surer. A quiet pulse on the help button, which interrupts nobody, starts at 45. The rule of thumb: the more a piece of help interrupts, the more evidence it needs.
Check that the help actually helped
After a tip shows, keep watching the same element. If the person clicks the disabled button again, rage clicks, gets a dead click or an error, the tip didn't solve the problem. Only real attempts count here, not resting the pointer on the button while reading the tip. This is the most useful report you can give a product team: "we showed help on this button 40 times and people were still stuck 30 times" usually means the button itself needs fixing, not more help.
Building this yourself
- Listen to clicks and pointer events on the document, in the capture phase, and walk up to the nearest interactive element (a click on an icon belongs to the button around it).
- Detect "nothing changed" with one MutationObserver plus checks for navigation, focus and scroll, all within a short window after the click.
- Rely on ARIA attributes for errors and menus. If your app doesn't set aria-invalid or aria-expanded, fix that first: it helps screen reader users too.
- Keep everything in the browser. You don't need to send raw clicks to a server to decide whether to show a tip.
Cueston does all of this out of the box: add a tooltip, choose "When someone struggles here" as its trigger, and it uses the signals and thresholds above.
Frequently asked questions
What is a rage click?▾
Three or more clicks on the same element within about one second. To avoid false alarms, at least two of those clicks should have changed nothing on the page, since some controls like steppers react to every click.
What is a dead click?▾
A click on a button or link that produces no visible reaction: no DOM change, no navigation, no focus or scroll change within roughly 0.65 seconds.
Should I show help after one rage click?▾
No. One rage click is often just impatience or a slow network. Show help when a strong signal repeats or two different signals happen on the same element.