Border Ripple
A button where every click, by finger or keyboard, sends out three expanding border ripples and a light that travels around the frame.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Utilities has 43 effects, including 3 free. Explore the category →
3 usage examples



How it works
The effect is a real <button type="button"> with the brr-box class: a pale violet 2 px border that gets a little stronger on hover. It holds three <span class="brr-pulse"> (2 px frames, invisible at rest) and a <span class="brr-travel"> carrying the light, then your text.
On every click (mouse, finger, Enter or Space), the script restarts the three frames 200 ms apart: each one starts against the button, moves out to 20 px while rounding its corners and fades in 1 s. At the same time the brr-clicking class starts the light: a 40 px #a78bfa gradient glow running along the top, right, bottom then left in 1.2 s, clipped to the button's edge. Clicking again before the end cancels the pending timers and restarts everything from the beginning.
The script looks for each .fx-0773-root and the button inside it: each copy has its own ripples and timers, and pasting the script twice does not add a second listener. The animations are called brr-pulse and brr-travel, names no other effect in the library uses.
Accessibility
- Real button: it gets focus with Tab, fires with Enter or Space and is announced as "button" followed by its text. On
:focus-visible, a 2 px outline shows 4 px from the edge. (The original version was a<div>that the keyboard could not reach.) - Silent decoration: ripples and light are empty
<span>witharia-hidden="true"; the button's name is just its text. - Reduced motion: under
prefers-reduced-motion: reduce, no ripple and no travelling light; the click lights the outline (light border, 4 px ring and glow) for 1.2 s, with no transition, then everything returns to rest. - Visual feedback only: the ripple tells screen readers nothing. If the click has a result (order sent, task ticked), announce it in text, for instance in an
aria-live="polite"region.
Browser compatibility
CSS (animation of the inset property: Chrome 87, Firefox 66, Safari 14.1; :focus-visible: Chrome 86, Firefox 85, Safari 15.4) and 1.2 KB of JavaScript. Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two copies with only the second one clicked, Tab then Enter, finger press, 390 px without overflow, reduced motion with no animation after the click, 0 errors.
Safari 14.1 to 15.3: everything works, with the browser's focus ring instead of the violet outline. Without JavaScript the button stays a button, with no ripple.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0773-root">
<button type="button" class="brr-box">
<span class="brr-pulse" aria-hidden="true"></span>
<span class="brr-pulse" aria-hidden="true"></span>
<span class="brr-pulse" aria-hidden="true"></span>
<span class="brr-travel" aria-hidden="true"></span>
…
</button>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Ripple colour (CSS — --brr-color) |
#6366f1 |
Colour of the three expanding frames. |
Light colour (CSS — --brr-light) |
#a78bfa |
Travelling glow, focus outline and reduced-motion glow. |
Border at rest and on hover (CSS — --brr-rest, --brr-hover) |
rgba(99, 102, 241, .3) and .5 |
Colour of the 2 px border; --brr-hover is also the ring under reduced motion. |
Ripple reach (CSS — inset: -20px in @keyframes brr-pulse) |
20 px | How far each ripple goes. Leave at least that much free space around the button. |
Delay and count (JS — i * 200, HTML — .brr-pulse) |
3 ripples, 200 ms apart | Add or remove <span class="brr-pulse">; the script picks them all up. |
Size (CSS — width / min-height of .brr-box) |
at least 200 × 120 px | The light follows the edge at any size, from a list row to a full-screen button. |
FAQ
brr-box class to your <button>, the four <span> as first children, and wrap it in a .fx-0773-root, one root per button: the script only wires the first button of each root. Keep it for actions that stay on the page: on a link that changes page, the ripple would not have time to be seen.overflow: hidden or the button touches the edge of its container, the part that sticks out is clipped. Leave a 20 px margin, or reduce the reach in @keyframes brr-pulse.