A button that sends a ripple out from where you pressed it, or from its centre when pressed with the keyboard. Builds on Button.
Optional, and kept apart.
Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.
import { RippleButton } from "@rdloom/react";
export default function RippleButtonBasicExample() {
return (
<RippleButton>Press me</RippleButton>
);
}Installation
npx rdloom add ripple-buttonCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.
It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/ripple-button.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { RippleButton } from "@rdloom/react";
<RippleButton>Press me</RippleButton>Variants
import { RippleButton } from "@rdloom/react";
export default function RippleButtonVariantsExample() {
return (
<div className="flex flex-wrap items-center gap-3">
<RippleButton>Primary</RippleButton>
<RippleButton variant="secondary">Secondary</RippleButton>
<RippleButton variant="ghost">Ghost</RippleButton>
<RippleButton duration={1000}>Slow ripple</RippleButton>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
childrenrequiredButton label. Icon-only buttons must pass aria-label instead. | node | none |
variantVisual emphasis, as on Button. | "primary" | "secondary" | "ghost" | "danger" | "primary" |
sizeHeight, padding and font size, as on Button. | "sm" | "md" | "lg" | "md" |
durationMilliseconds the ripple takes to spread and fade. | number | 600 |
isDisabledPrevents interaction and dims the button. | boolean | false |
isLoadingShows a spinner and blocks presses; the pending state is announced. | boolean | false |
onPressCalled when the button is pressed by mouse, touch or keyboard. | () => void | none |
Accessibility
Role button, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab focuses it
- Enter or Space presses it
Screen readers announce
- Announced exactly as a Button: its label, as a button; the effect adds nothing to what is read
What your code must do
- Decoration only: the animated layers are hidden from assistive technology and never take focus
- Stands still for visitors who ask for less motion (prefers-reduced-motion), and the effect can also be shown still with the rdm-still class
- Each ripple is removed when it finishes, so a button pressed many times does not grow
- Does not flash: nothing changes more than three times in a second (WCAG 2.3.1)
- Optional: it is a separate component, so projects that do not add it carry none of its code
Guidelines
Use it when
- Touch-first interfaces that want visible press feedback
- Playful or media apps
Avoid it when
- Buttons that are pressed many times a second
- When a plain pressed state is enough
Don't
- A duration over about a second
- Stacking it with other effects on the same button
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-action-primary--rd-color-action-on-primary--rd-color-action-danger--rd-color-surface-default--rd-color-text-default--rd-color-border-default--rd-color-focus-ring--rd-radius-control