Actions should run when the pointer is released, so people can slide off a control to cancel. Examples from payments and filings.
Anyone can put a finger down in the wrong place. People with tremor, limited dexterity or poor aim do it far more often. WCAG 2.1 success criterion 2.5.2 asks that, for functions operated with a single pointer, at least one of these is true:
The most dependable route is the first: let the function run on release, as native click and button activation already do. The user can slide the pointer off the target before letting go and nothing happens.
The riskiest controls on a government site are the ones that cannot be taken back: Pay now on a utility bill page, Submit on a court e-filing, Cancel reservation on a county clinic booking. When these fire on touch-down, a missed tap becomes a real transaction. The Title II rule applies WCAG 2.1 to web content and mobile apps a public entity provides, so the way a button reacts to a press is part of what gets tested. Related: common barriers.
<div role="button" tabindex="0" onpointerdown="payBill()">
Pay $84.20
</div><button type="button" id="pay-bill">Pay $84.20</button>
<script>
document.getElementById("pay-bill")
.addEventListener("click", payBill); // runs on release
</script>submitFiling.addEventListener("pointerdown", () => {
sendFiling(caseNumber);
});submitFiling.addEventListener("click", () => {
sendFiling(caseNumber);
});document.addEventListener("pointerup", e => {
dropChoice(e.target); // always commits where released
});let dragging = false;
document.addEventListener("keydown", e => {
if (e.key === "Escape" && dragging) { cancelDrag(); dragging = false; }
});
document.addEventListener("pointerup", e => {
if (!dragging) return;
if (!isValidDropZone(e.target)) { cancelDrag(); return; } // released outside: abort
dropChoice(e.target);
showUndo("Moved to choice 2", undoLastMove); // and offer undo
dragging = false;
});A SwiftUI Button and a Compose clickable or Button already activate on release inside the control. Problems appear when developers build their own touch handling.
// SwiftUI
Text("Pay $84.20")
.gesture(DragGesture(minimumDistance: 0)
.onChanged { _ in payBill() }) // fires on first touch// SwiftUI
Button("Pay $84.20") {
payBill() // fires on release inside
}// Jetpack Compose
Box(Modifier.pointerInput(Unit) {
detectTapGestures(onPress = { payBill() }) // fires on press
}) { Text("Pay $84.20") }// Jetpack Compose
Button(onClick = { payBill() }) { // fires on release
Text("Pay $84.20")
}mousedown, touchstart, pointerdown and onPress handlers that call business functions. Each hit needs a reason.mousedown or pointerdown to feel snappier.