Pressed Button Feedback
A button that doesn't visibly react to a click reads as broken, even if the action fires correctly. The interface needs to acknowledge the press before the async result comes back.
When to use
- Any clickable/tappable control: buttons, icon buttons, cards acting as links, toggle switches.
- Especially important on touch devices, where there's no hover state to rely on.
Principles
- Feedback has three phases: press (instant, <16ms), hold (while pointer is down), release (returns to rest or transitions to a loading/success state).
- The press state should be visually distinct but subtle — a scale or brightness change, not a layout shift.
- Feedback must never wait on the network. Show the pressed state immediately; handle the async result separately (loading spinner, disabled state, error).
Instructions
- Add a
:active(oronPointerDown) state that triggers within one frame — no transition delay on press-in. - Use
scale(0.97)or a slight opacity/brightness dip, not a color change that could be mistaken for a disabled state. - Transition back out over 100–150ms on release so it doesn't feel sluggish.
- If the action is async, disable the button and show a loading indicator only after the pressed feedback has played — don't skip straight to a spinner.
- Respect
prefers-reduced-motion: fall back to an opacity-only change instead of a scale transform.
Examples
button {
transition: transform 120ms ease-out, opacity 120ms ease-out;
}
button:active {
transform: scale(0.97);
transition-duration: 0ms;
}
Avoid
- Don't rely on the browser's default focus ring as your only feedback — it doesn't fire on click in most browsers.
- Don't animate width/height/padding for press feedback; it causes layout shift and feels laggy.
- Don't skip feedback on "instant" actions just because they resolve fast — sub-100ms actions still benefit from a press state.
References
- Native iOS/Android touch feedback (
UIButtonhighlight, Material ripple) as the baseline expectation users already have.