Skip to content
Motion & micro-interactionsLesson 5 of 8Beginner8 min

Tap & gesture feedback

Touch interfaces lack physical clicks, so a tap needs visible feedback — a ripple and a small press — to confirm the surface felt you.

Kept on this device only.

In one line: a touchscreen gives no click and no travel, so a tap needs a visible answer — a ripple from your fingertip and a small press-in — to say the surface felt you.

What it is

Tap feedback is the visible reply a control gives the instant you touch it. On a physical button you feel the give and hear the click; a glass screen offers neither, so the interface has to draw the sensation instead. Two moves carry most of the weight: a ripple that grows outward from the exact point you pressed, and a press — the surface scaling down a hair as if your finger pushed it in, then recovering when you let go.

The ripple's job is location: it confirms which spot registered, starting where your fingertip landed rather than at some fixed center. The press's job is force: it echoes the act of pushing. Together they turn a flat state change into a small physical event.

Why it matters

On touch there's a gap between contact and result — the network call, the route change, the sheet that slides up. Without immediate feedback, that gap reads as did it even work?, and people tap again. A ripple and a press close the gap instantly: the surface answers in the first frame, before the real work finishes, so the tap feels acknowledged even when the outcome is still loading.

It also builds trust through precision. A ripple that springs from your fingertip says the interface saw exactly where you touched — not a vague region, that point. The restraint matters as much as the effect: a slow ripple that lingers, or a press so deep the control visibly shrinks, reads as gimmicky and adds lag to every interaction. The feedback should be quick and almost subliminal — felt, then gone before you think about it.

See it

Live demo
Tweak it2
1
Press anywhere on the surface. Try it with a keyboard too.

How it works

Four moves, and one fallback:

  1. Origin at the pointer. On pointer-down, read the press coordinates relative to the surface (clientX/Y minus the element's bounding rect) and place the ripple there. That single offset is what makes it feel located rather than generic.
  2. Clip to the control. The ripple is a circle that grows past the surface edges, so the surface needs overflow: hidden. Without it the circle bleeds out and the effect falls apart.
  3. Expand and fade. Animate the ripple from scale: 0 up and from a low opacity down to zero in one short pass. Only transform and opacity move, so it stays on the compositor.
  4. Press and recover. On press, scale the whole surface down a few percent; on release, spring it back. The recovery is a spring so it feels like a physical rebound, not a scripted reset.
  5. Keyboard fallback. A keyboard user has no pointer coordinates, so on Space/Enter center the ripple in the surface. The press and recovery are identical — the interaction shouldn't feel second-class on a keyboard.

Build it

Vanilla CSS + a sprinkle of JS
.tap-surface {
  position: relative;
  overflow: hidden; /* clip the ripple to the control */
  transform: scale(1);
  transition: transform 120ms ease;
}
 
/* press in while held; recovery is the transition above */
.tap-surface:active {
  transform: scale(0.97);
}
 
/* one ripple element, positioned by JS at the press point */
.ripple {
  position: absolute;
  width: 12rem;
  height: 12rem;
  margin: -6rem 0 0 -6rem; /* center the circle on its x/y */
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.3);
  transform: scale(0);
  opacity: 0.5;
  pointer-events: none;
  animation: ripple 600ms ease-out forwards;
}
 
@keyframes ripple {
  to {
    transform: scale(2.6);
    opacity: 0;
  }
}
 
/* JS: on pointerdown, place + spawn a .ripple, then remove it on animationend
   const r = el.getBoundingClientRect();
   ripple.style.left = (e.clientX - r.left) + "px";
   ripple.style.top  = (e.clientY - r.top)  + "px";
   For keyboard (Space/Enter), use the surface center: r.width / 2, r.height / 2. */
 
@media (prefers-reduced-motion: reduce) {
  .tap-surface,
  .tap-surface:active {
    transition: none;
    transform: none;
  }
  .ripple {
    animation: none;
    display: none; /* calm, instant highlight instead */
  }
}

Make it yours

Use the controls beside the demo above to change show ripple and press depth — each change updates the example live.

Experiment in the playground

Reproduce it with an LLM

Reproduce it with an LLM

You are a front-end engineer building for touch and pointer. Add tactile feedback to a button: on press it shows a brief radius-bounded ripple originating at the pointer position and a subtle scale-down, recovering on release. Build it in React with the Motion library. Requirements: it must be a real <button> with keyboard activation (the ripple may center itself for keyboard activation); a visible focus-visible ring; the ripple must be clipped to the button (overflow hidden) and animate only transform/opacity; fully disable the ripple and scale under prefers-reduced-motion: reduce. No external CSS framework. Return only the component code with imports.

Pitfalls & accessibility

Further reading

  • Your animation library's gesture and AnimatePresence docs, for spawning and unmounting transient elements like ripples cleanly.
  • The pointer events spec, for handling touch, pen, and mouse through one unified set of handlers.