Button Press

A real button moves when you push it. A button on screen can borrow that with one rule: scale it down a little while it is pressed. It is the cheapest feedback the web has, and it makes the button feel like it heard you.

Wrong
Right

The one on the right is not doing much. It shrinks to 97% for as long as your finger is down, then eases back to full size.

The goal with press feedback is to make it invisible. Nobody should think "oh, the button scales". If they do, the animation is too much.

Finding the amount

Two numbers matter: how far the button shrinks and how quickly it gets there.

I like 0.98 or 0.97 for the scale. Below 0.95 it starts to look like an effect, and people notice the animation instead of the click. For the duration, I usually go somewhere between 100ms and 150ms. Longer than that and the button lags behind your finger, which feels worse than no feedback at all.

Not just buttons

This works on other clickable things too: cards, tiles, chips. Just be careful not to overdo it.

Larger surfaces should shrink less. A full-width card at 0.97 moves several pixels at the edges, which is a lot more than a button does at the same scale.

Usage

app.tsx
<button
  class="transition-transform duration-100 ease-out active:scale-[0.97] disabled:active:scale-100 motion-reduce:transition-none [-webkit-tap-highlight-color:transparent]"
>
  Save changes
</button>
.button {
  transition: transform 100ms ease-out;
  -webkit-tap-highlight-color: transparent;
}

.button:active:not(:disabled) {
  transform: scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  .button {
    transition: none;
  }
}

Transition transform only, not all, so the press timing does not leak into hover colors. A disabled button should not press, and on iOS it is worth clearing the grey tap highlight so it does not flash over the scale.

iOS Safari also only applies :active while something on the page listens for touches. Frameworks like React already do, but on a plain page add an empty listener, or the press never shows:

app.js
button.addEventListener("touchstart", () => {}, { passive: true });

Resources

Search concepts

Search for a command to run...