A short burst of paper pieces, launched from a fire() handle or when isActive turns true. The canvas appears for the burst, then removes itself.
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 { useRef } from "react";
import { Button, Confetti, type ConfettiHandle } from "@rdloom/react";
export default function ConfettiWithButtonExample() {
const confetti = useRef<ConfettiHandle>(null);
return (
<div className="flex w-full justify-center">
<Confetti ref={confetti} />
<Button onPress={() => confetti.current?.fire({ origin: { x: 0.5, y: 0.7 } })}>Celebrate</Button>
</div>
);
}Installation
npx rdloom add confettiCopies 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/confetti.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { Button, Confetti, type ConfettiHandle } from "@rdloom/react";
<div className="flex w-full justify-center">
<Confetti ref={confetti} />
<Button onPress={() => confetti.current?.fire({ origin: { x: 0.5, y: 0.7 } })}>Celebrate</Button>
</div>On active
Press the button when you are done
import { useState } from "react";
import { Button, Confetti } from "@rdloom/react";
export default function ConfettiOnActiveExample() {
const [done, setDone] = useState(false);
return (
<div className="flex w-full flex-col items-center gap-3">
<Confetti isActive={done} particleCount={60} onComplete={() => setDone(false)} />
<Button onPress={() => setDone(true)}>Finish the task</Button>
<p role="status" className="text-sm text-[var(--rd-color-text-muted)]">
{done ? "Task complete" : "Press the button when you are done"}
</p>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
isActiveFires a burst each time it changes to true. | boolean | false |
particleCountPieces per burst. | number | 80 |
spreadSpread of the burst in degrees. | number | 70 |
originWhere the burst starts, as fractions of the viewport. | { x: number; y: number } | none |
colorsColours of the pieces, any CSS colours. Defaults to the theme colours. | string[] | none |
onCompleteCalled when the last piece has gone. | () => void | none |
isPausedStops the effect: fire() then does nothing. | boolean | false |
Accessibility
Role presentation, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
Not interactive.
Screen readers announce
- Silent: the canvas is hidden from assistive technology, so announce the success separately
What your code must do
- Decoration only: the canvas is aria-hidden and pointer-events: none, so it never blocks a click or takes focus
- Nothing at all is drawn for reduced motion, inside rdm-still or with isPaused
- The burst lasts about three seconds and then the canvas is removed from the page
- A celebration must also be stated in text: pair it with a status message (WCAG 4.1.3)
- No flashing: pieces fall and fade, nothing blinks
Guidelines
Use it when
- After a completed task, a purchase or a milestone
Avoid it when
- Errors or routine saves
- Anywhere the burst could hide controls the person still needs
Don't
- Using confetti as the only sign of success
- Firing it repeatedly
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-action-primary--rd-color-feedback-success--rd-color-feedback-warning--rd-color-feedback-danger