Skip to content
usefy

Confetti

ComponentsCelebration engine

Canvas confetti & celebration engine for React — hand-written physics, zero dependencies, three consumption layers

Live demo

Install

$ npm install @usefy/confetti

Quick start

Confetti.tsx
import { fireConfetti } from "@usefy/confetti";

function ShipItButton() {
  return (
    <button onClick={() => fireConfetti({ origin: { y: 0.8 }, spread: 70 })}>
      🚀 Ship it
    </button>
  );
}

API reference

<Confetti /> — overlay with an imperative controller

import { useRef } from "react";
import { Confetti, type ConfettiController } from "@usefy/confetti";

function CheckoutSuccess() {
  const confetti = useRef<ConfettiController>(null);

  return (
    <>
      <Confetti
        controllerRef={confetti}
        onComplete={() => console.log("settled")}
      />
      <button onClick={() => confetti.current?.fire({ origin: { y: 0.7 } })}>
        Complete purchase
      </button>
    </>
  );
}
PropDefaultDescription
variant"overlay""overlay" portals a fixed full-viewport canvas after hydration; "inline" fills the parent element (give it position: relative)
zIndex1100Overlay stacking order (above @usefy/spotlight-tour's 1000)
controllerRefReceives { fire, emit, stop, clear } (stable identity)
onCompleteCalled on each idle edge (all particles dead, no emitter)
fireOnMountfalsetrue or a FireOptions — one burst after mount (respects reduced motion)
reducedMotion"respect""respect" no-ops under prefers-reduced-motion; "ignore" animates anyway
className / styleMerged onto the canvas element

The canvas is aria-hidden with pointer-events: none — it never intercepts input, and the engine pauses automatically while the tab is hidden.

useConfetti() — a canvas you own

import { useConfetti } from "@usefy/confetti";

function CardCelebration() {
  const { canvasRef, fire, isActive } = useConfetti();

  return (
    <div style={{ position: "relative", overflow: "hidden" }}>
      <canvas
        ref={canvasRef}
        style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }}
      />
      <button disabled={isActive} onClick={() => fire({ origin: { y: 0.9 } })}>
        {isActive ? "Celebrating…" : "🎉 Celebrate"}
      </button>
    </div>
  );
}

Returns { canvasRef, fire, emit, stop, clear, isActive } — all functions stable; isActive re-renders only on idle↔active edges, never per frame.


Go deeper

This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.

More in components