WebberUI

Confetti Feedback

A hand-built particle confetti celebration — bursting from an origin point, each piece with its own initial velocity, gravity, rotation, and drift, updated on rAF and reclaimed once it lands.

Loading preview…
npx shadcn@latest add https://webberui.com/r/confetti-feedback.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

50
<ConfettiFeedback />

Installation

npx shadcn@latest add https://webberui.com/r/confetti-feedback.json

Or, once registries are configured in components.json, install it as @webberui/confetti-feedback.

Usage

The simplest way in is ConfettiButton, which bursts confetti from the center of the button on click and runs onClick as usual:

import { ConfettiButton } from "@/components/ui/confetti-feedback";

<ConfettiButton onClick={() => save()}>Celebrate 🎉</ConfettiButton>

When you need to burst at an arbitrary moment from arbitrary coordinates, use the useConfetti hook instead. It returns fire plus a confetti node that must be rendered into the page:

import { useConfetti } from "@/components/ui/confetti-feedback";

function CheckoutDone() {
  const { fire, confetti } = useConfetti();

  return (
    <>
      <button
        onClick={(e) => {
          const r = e.currentTarget.getBoundingClientRect();
          fire({ x: r.left + r.width / 2, y: r.top + r.height / 2 });
        }}
      >
        Done
      </button>
      {confetti}
    </>
  );
}

In fire(origin?, options?), origin is in viewport coordinates (defaulting to the top center of the screen when omitted), and options can carry particleCount and colors.

Props

ConfettiButton

Forwards all native button attributes, plus:

PropTypeDefaultDescription
particleCountnumber50Number of particles per burst, clamped between 0 and 80
colorsstring[]seven built-in colorsCandidate confetti colors, picked at random per piece
onClick(e) => voidRuns as usual after the burst
classNamestringAppended to the button styling

useConfetti()

ReturnsTypeDescription
fire(origin?, options?) => voidTriggers one burst; origin is in viewport coordinates { x, y }
confettiReact.ReactNodeThe node you must render; internally it is stacked on top via createPortal

options supports particleCount (capped at 80) and colors.

How it works and accessibility

  • Particles are small DOM color blocks whose transform (offset, rotation, drifting sway) is updated frame by frame on rAF, with no React re-render
  • Each piece of confetti carries a random initial velocity and gravity, and is reclaimed once it falls past the bottom of the viewport or exceeds its maximum lifetime; once a whole burst is cleared it unmounts automatically
  • dt is capped, so particles do not teleport when the tab is switched back or frames are dropped
  • A single burst is capped at 80 particles, and rapid clicking keeps only the most recent few bursts — older bursts unmount and their rAF is stopped
  • cancelAnimationFrame cleans up on unmount, so nothing updates after unmounting
  • The layer is decorative only, carrying aria-hidden and pointer-events-none so it does not interfere with the pointer or with assistive technology
  • When the user has "reduce motion" enabled at the system level, nothing bursts at all, but the button's onClick action still runs

On this page