Ring the press, not the fill

A ring animation shows on a fast tap, and stays apart from hover.

Sam Selikoff@samselikoff
Noticed this ring/outline animation on press this morning. First time seeing this treatment, I like it. Much more subtle than the typical background "ripple" (which always felt gratuitous to me). The animation (rather than transition) means it works even for a fast tap. And using the border rather than the background lets you differentiate completing the press from both hover and mouseDown. Nice!
Sep 10, 2025View on X ↗

1 · Tap fast

Tap each button quickly. Hover only tints the background.

:active transitionBad
:active { box-shadow }Presses0
Ring animationGood
el.animate(ring)Presses0
button.addEventListener("pointerdown", ring);
button.addEventListener("keydown", (e) => {
  if (!e.repeat && (e.key === "Enter" || e.key === " ")) ring();
});

const ring = () => button.querySelector(".ring").animate(
  [{ boxShadow: "0 0 0 0 var(--ring-color)", opacity: 1 },
   { boxShadow: "0 0 0 6px var(--ring-color)", opacity: 0 }],
  { duration: 350, easing: "cubic-bezier(0.23, 1, 0.32, 1)" });

2 · Ripple or ring

Press each button. The ripple fills the whole surface.

Background rippleBad
ripple from pointerPresses0
Ring outlineGood
ring on the edgePresses0

Why it matters, pros and cons

Why it matters

An animation runs to the end even on a 60ms tap, so every press gets seen.

Pros

  • Subtler than a ripple, which fills the surface and draws too much.
  • The edge ring tells a completed press apart from hover and mousedown.
  • Starts on pointerdown and on Enter or Space, so mouse, touch and keys agree.

Cons

  • Needs a restart on each press, or a rapid second tap shows nothing new.
  • A ring on a button that sits flush with others can get clipped.
  • Under reduced motion it has to become a fade, not a moving ring.