Ripple Button
A button with a spreading click ripple, radiating a Material-style wave out from the click point.
On click, a ripple radiates from the cursor's landing point, with a diameter reaching the farthest corner so the wave always covers the whole button. The ripple sits beneath the content and is clipped into the button's shape by overflow-hidden.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add ripple-button.enRipple Button is a React ripple effect button built with Motion and Tailwind CSS, with ripples mounted and removed through Motion's AnimatePresence. Rapid clicks each spawn their own ripple, and every ripple leaves the DOM once its animation ends.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<RippleButton/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Primary actions such as form submit or add to cart, where a clear press response helps
- Mobile web and PWAs that should feel like native touch feedback
- Admin toolbars and dialog footers with confirm and cancel buttons
- Not a good fit: buttons that need loading, success or error states; use Stateful Button instead
Installation
npx webberui@latest add https://webberui.com/r/ripple-button.en.jsonOr, once registries are configured in components.json, install it as @webberui/ripple-button.
Usage
import { RippleButton } from "@/components/ui/ripple-button";
<RippleButton onClick={() => console.log("clicked")}>
Click to ripple
</RippleButton>To change the color just pass rippleColor; all other <button> attributes (type, disabled, onClick, and so on) are forwarded.
Examples
Light button with a dark ripple
The default ripple is translucent white, which disappears on a light background, so change rippleColor along with the button colours.
<RippleButton
rippleColor="rgba(0, 0, 0, 0.12)"
className="border border-neutral-200 bg-white text-neutral-900 hover:bg-neutral-50"
>
Cancel
</RippleButton>className is merged with the defaults through cn, so background, text colour and radius can all be overridden directly.
Form submit button
Native attributes such as type and disabled pass through to the <button>. The content wrapper is inline-flex gap-2, so an icon and label are spaced automatically.
<RippleButton type="submit" disabled={isSubmitting}>
{isSubmitting && <Spinner className="size-4" />}
{isSubmitting ? "Submitting" : "Place order"}
</RippleButton>Spinner stands for the loading icon component in your project.
Brand colour with a slower ripple
duration is in seconds, and opacity is the ripple's starting opacity before it fades to 0.
<RippleButton
rippleColor="rgba(255, 255, 255, 0.7)"
duration={1}
opacity={0.8}
className="bg-indigo-600 text-white hover:bg-indigo-500"
>
Create project
</RippleButton>Props
Apart from the props below, all native <button> attributes are forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
rippleColor | string | "rgba(255, 255, 255, 0.55)" | Ripple color; any CSS color value |
duration | number | 0.6 | Duration of a single ripple's spread (seconds) |
opacity | number | 0.5 | Starting opacity of the ripple |
className | string | — | Forwarded to the button; can override the default styles |
children | ReactNode | — | Button content |
Accessibility
- When the user has "reduce motion" enabled at the system level, the ripple is skipped and only the ordinary button behavior remains
- The ripple layer carries
aria-hiddenandpointer-events-none, so it affects neither assistive technology nor clicks - The native
<button>semantics and keyboard focus ring are preserved, and interaction is disabled whiledisabled
FAQ
Does it work with the Next.js App Router?
Yes. The file already starts with "use client", so import it from a Server Component page as usual; event handlers such as onClick need to be passed from a Client Component.
Why doesn't the ripple spill outside the button?
Ripples sit in an overflow-hidden, rounded-[inherit] layer, so they are clipped to the button's shape. The diameter is twice the distance from the click point to the farthest corner, so a click anywhere covers the whole button.
Can I use it as a link?
It always renders a <button> and has no asChild-style option. To navigate, call your router's navigation method in onClick, or edit the source to render a link element.
Does my own onClick still run?
Yes. The component spawns the ripple first and then calls your onClick; style is also merged with the CSS variable it sets internally.
Does it respect reduced motion?
Yes. With "reduce motion" on, no ripples are created and the button behaves like a regular button.