Search topics
Buttons that act on press and can't be cancelled
The button runs its action on pointerdown, so a press that lands on it by mistake takes effect at once, and sliding off before letting go can't stop it.
addEventListener('pointerdown', addToBasket) The problem
People whose press lands on the wrong button can't take it back by sliding off before they let go.
On almost every button people use, touching it is not yet a decision; letting go is. In between, anyone who touched the wrong thing can slide off and change their mind, and most people do it without noticing. A shop lists its products in cards, each with an Add to basket button. To make the button feel faster, it adds the item the moment it is pressed, not when it is released. Press it, realise it's the wrong towel and slide off before letting go: the basket count has already gone up. On a phone it is worse. A finger that starts a scroll on the button adds the item as the page starts to move, so scrolling down a list of products quietly fills the basket.
WCAG 2.5.2 Pointer Cancellation asks that a single pointer press can be taken back. The function must not run on the down-event at all, or it runs on the up-event with a way to abort it before release or undo it after, or the release reverses what the press did, or running on the press is essential, as on a piano keyboard. Here the whole action runs on pointerdown, the down-event, and nothing about it is essential. An undo button would not rescue it either: the criterion's undo route applies only to an action that completes on the up-event. W3C lists this exact pattern as failure F101, activating a control on the down-event.
Anyone scrolling a long product list with a thumb meets the same failure, and it spreads to every touch screen at once: a phone, a tablet or a touch laptop all fire the press as the finger lands.
3 of 10 groups affected
- No vision (not affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited reach and strength People with tremors often land on the wrong button or touch one on the way to another. Sliding off before letting go is how they recover, and here it does nothing: the item is already in the basket.
- Limited manipulation People using a head pointer, a stylus held in the mouth or a pointer they steer slowly often make contact before they have finished aiming, so the first touch counts as a press.
- Limited vision People using a magnifier see little of the page around the pointer, and often press first and check what is under it while holding. A press that acts at once leaves no time to check.
The button has a name, a role and a focus ring, so every markup check passes. A scripted test calls click() or presses and releases at the same point in a single step, and both work on the broken button. Nothing that runs the happy path ever slips. A scanner can't see which event a script listens for, since frameworks often attach listeners away from the element, and a down listener is not a failure by itself: many buttons use one to draw a ripple. AI tools often suggest onMouseDown or onPointerDown to make a control feel faster, or to stop a text field losing focus first, and the result works perfectly in a demo with a steady hand.
Try it
Press and hold Add to basket, and watch when the item goes in. Then slide off before you let go.
Touching a button is not deciding; letting go is. That gap is how anyone who touches the wrong button, or only meant to scroll, changes their mind. The panel under the shop shows whether this one waits. On a phone, also scroll the list starting on a button.
Broken Can you fix this?
- Linen tea towel
- Cotton apron
- Oak chopping board
- Enamel mug
The fix
Move the action to click, and let the press do nothing but show that it's pressed.
- 1Run actions on release over the control, never on press.
- 2Sliding off before letting go cancels, and a scroll never counts as a press.
- 3A down listener may show a pressed look, keep focus or start a drag, but never complete the action.
<button type="button" class="add">Add to basket</button><button type="button" class="add">Add to basket</button>Why this fix: Use native HTML first
pointerdown fires for a mouse, a pen and a finger the instant they touch the button, so the item is added before the person can see what they pressed or move away. On touch it also fires at the start of every scroll that begins on the button. mousedown, touchstart and a framework's onMouseDown or onTouchStart fail the same way.
Delete both listeners and use the button's own click. The browser fires click only when the press and the release both happen on the button, so sliding off before letting go cancels it, and a touch that turns into a scroll never becomes a click. The same event fires for Enter and Space on a focused button, for a screen reader's double tap and for voice control, so the separate keyboard code goes too. The click on a button that has already been released feels just as fast: the time between press and release is what the person spends deciding.
A down listener is fine as long as it never completes the action. These uses are common and pass.
- A pressed look:
:activein CSS shows it with no script, and a ripple drawn onpointerdownis fine while the action waits forclick. - Keeping focus: a suggestion list chosen with
mousedownusually does it so the search field doesn't lose focus and close the list first. Keep themousedown, but only to callevent.preventDefault(), which stops the focus moving, and choose the option onclick. Autocomplete fields with silent, mouse-only suggestions covers the rest of that list. - Starting a drag: the drop happens on release, and dropping outside any target must put the item back. Drag-and-drop boards that only a mouse can use covers the other ways to move it.
- Press and hold: a preview that appears while held and disappears on release passes, because the release reverses the press. A hold that commits something, such as "hold to delete", must still finish on release and cancel when the pointer leaves.
Some things that look like fixes are not. pointerup, mouseup and touchend are up-events, but they don't cancel. In Chromium, touchend fires on the button even after the finger has scrolled the page, and with a mouse, pointerup fires on a button the pointer arrives at after pressing somewhere else. setPointerCapture() on a plain button routes the release back to it, and the browser then fires click on the button even when the pointer was released far away, so leave capture to things that are really dragged.
Component libraries often wrap this in a press event. Use the one that fires on release over the control, such as onPress, not a "press start" event, and try the slide-off test on it rather than trusting the name. A link needs nothing: following a link is already a click. For an action with lasting effects, such as deleting or paying, release alone is not much protection, so add an undo or a confirmation as well.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
5 checks.
Test with: Mouse, Touch, Keyboard, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared button or card component changes.
Limits
This is a starting pattern, not a guarantee for every application. Some controls must act on the press because that is what they are, such as the keys of an on-screen keyboard or a musical instrument; WCAG treats those as essential. A small button can still be missed in the first place: tap targets too small and too close together covers its size. Test the complete journey on real touch devices as well as with a mouse.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.