@neoconfetti/react
@neoconfetti/react is a small React component that creates a one-shot confetti burst with CSS animations and ordinary DOM elements. Mount <Confetti> at the burst origin and it generates colored circles or rectangles, animates them across a configurable stage, then removes the particle nodes after 3.5 seconds by default. It bundles the shared neoconfetti engine, includes TypeScript declarations, and performs its browser work inside an effect, so server rendering produces only an empty container.
A focused choice for a small, one-shot React celebration when a few hundred temporary DOM nodes are acceptable. Pick canvas-confetti for frequent bursts, richer physics, or tighter performance limits, and treat this package's single-release history as a maintenance risk.
Use it if
- You need a small, decorative confetti burst at a button, badge, dialog, or success message rather than a continuous full-screen simulation
- You want a React component with built-in TypeScript declarations and no runtime configuration file or asset pipeline
- You need to control particle count, size, shape, colors, force, duration, and stage dimensions through props
- You prefer CSS-animated DOM particles and want the generated nodes removed automatically when the burst finishes
- You need hundreds of particles on low-end phones: the README says every particle creates two DOM nodes, so the default 150 particles add 300 nodes and counts of 400 or 500 should be tested on target devices
- You need an imperative fire(), pause, origin angle, gravity, drift, or completion callback API: the published type declarations expose one component and a short set of visual and stage props, with none of those controls
- Your codebase consumes packages through CommonJS require(): the 1.0.0 package is type: module and its export map provides import and default ESM files but no require condition
- You need a mature release history: 1.0.0 is the only published version, it was released in September 2024, and the repository was last pushed in March 2025
- You want canvas rendering or a tiny fixed node count for repeated effects: this library creates real nested elements for every particle and injects its animation stylesheet into document.head
Setup reality
Installation is one npm command and there are no declared peer dependencies, native addons, credentials, configuration files, or external stylesheets. React is still required by the built output because it imports createElement, useEffect, and useRef, so the absence of a peerDependencies entry does not make the package usable without React. The package is ESM-only and marks its compiled file with use client, which suits modern bundlers and Next.js client components but can reject an older CommonJS toolchain. The component accepts class, not React's usual className; its published declaration confirms that spelling. Mounting it immediately starts a burst. To fire again, remount it with a new key or toggle it out and back in. Defaults are not viewport-aware: stageWidth is 1600, stageHeight is 800, particleCount is 150, and duration is 3500 milliseconds. The README warns that each particle creates two nodes, so the default temporarily adds 300 DOM elements. Nodes are cleared after the duration unless destroyAfterDone is false. Provide your own reduced-motion gate because the component has no built-in prefers-reduced-motion behavior. Also keep force between 0 and 1; the README says values outside that range throw.
Patterns
Render a confetti burstshow-basic-burst
import { Confetti } from '@neoconfetti/react';
export function SuccessBurst() {
return <Confetti />;
}The animation starts when the component mounts. With defaults it creates 150 particles and clears their nodes after 3500 milliseconds.
Remount the component for each clickreplay-on-click
import { useState } from 'react';
import { Confetti } from '@neoconfetti/react';
export function CelebrateButton() {
const [burst, setBurst] = useState(0);
return (
<button onClick={() => setBurst((n) => n + 1)} style={{ position: 'relative' }}>
Celebrate
{burst > 0 && <Confetti key={burst} class="burst" />}
</button>
);
}There is no fire method. A new key forces a fresh mount and therefore a fresh burst. The prop is named class, not className.
Use brand colors and circular particlesset-colors-and-shape
<Confetti
colors={['#6d28d9', '#ec4899', '#f59e0b']}
particleShape="circles"
particleCount={120}
particleSize={10}
/>Colors may be any valid CSS color strings. particleShape accepts only mix, circles, or rectangles.
Place the burst at the center of a cardposition-at-element
export function CompletedCard() {
return (
<section className="card">
<Confetti class="card-burst" stageWidth={480} stageHeight={320} />
<h2>Upload complete</h2>
</section>
);
}
// CSS
// .card { position: relative; overflow: hidden; }
// .card-burst { position: absolute; left: 50%; top: 45%; }The generated container has zero width and height. Position that origin yourself, and size the stage to the region where particles should travel.
Burst where the user clicksburst-at-pointer
import { useState } from 'react';
import { Confetti } from '@neoconfetti/react';
export function ClickStage() {
const [burst, setBurst] = useState<{ id: number; x: number; y: number }>();
return (
<div className="stage" onClick={(e) => {
const box = e.currentTarget.getBoundingClientRect();
setBurst({ id: Date.now(), x: e.clientX - box.left, y: e.clientY - box.top });
}}>
{burst && (
<div style={{ position: 'absolute', left: burst.x, top: burst.y }}>
<Confetti key={burst.id} stageWidth={600} stageHeight={500} />
</div>
)}
</div>
);
}Give .stage position: relative and overflow: hidden if particles must stay inside it.
Skip animation for reduced-motion usersrespect-reduced-motion
import { useEffect, useState } from 'react';
import { Confetti } from '@neoconfetti/react';
function AccessibleBurst() {
const [reduce, setReduce] = useState(true);
useEffect(() => {
const query = matchMedia('(prefers-reduced-motion: reduce)');
const sync = () => setReduce(query.matches);
sync();
query.addEventListener('change', sync);
return () => query.removeEventListener('change', sync);
}, []);
return reduce ? null : <Confetti />;
}Reduced-motion handling is not built in. Starting with true avoids briefly animating before the client preference is read.
Use a lower-cost mobile bursttune-for-mobile
<Confetti
particleCount={60}
particleSize={8}
duration={2200}
stageWidth={360}
stageHeight={640}
force={0.35}
/>Every particle creates two DOM nodes. Sixty particles means 120 temporary nodes, versus 300 nodes at the default count.
Measure a container for stage dimensionssize-to-container
import { useEffect, useRef, useState } from 'react';
import { Confetti } from '@neoconfetti/react';
function ResponsiveBurst() {
const host = useRef<HTMLDivElement>(null);
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
if (!host.current) return;
const observer = new ResizeObserver(([entry]) => {
setSize({ width: entry.contentRect.width, height: entry.contentRect.height });
});
observer.observe(host.current);
return () => observer.disconnect();
}, []);
return <div ref={host}>{size.width > 0 && <Confetti stageWidth={size.width} stageHeight={size.height} />}</div>;
}stageWidth and stageHeight are numeric pixels and do not follow the viewport automatically.
Attach a class to each particlestyle-particles
<Confetti particleClass="soft-confetti" />
// CSS
// .soft-confetti > div::before {
// filter: drop-shadow(0 1px 1px rgb(0 0 0 / 20%));
// }particleClass is applied to the outer node for every particle. The component's class prop applies only to the zero-size container.
Keep finished particle nodes temporarilykeep-particles-after-animation
<Confetti duration={5000} destroyAfterDone={false} />This prevents the duration timer from clearing the generated nodes. They still disappear when the component unmounts, but keeping them mounted costs memory and DOM size.
Create a typed reusable presetbuild-typed-preset
import { Confetti, type ConfettiProps } from '@neoconfetti/react';
const successPreset: ConfettiProps = {
colors: ['#16a34a', '#86efac', '#facc15'],
particleCount: 100,
force: 0.45,
duration: 2800,
};
export function SuccessConfetti(props: ConfettiProps) {
return <Confetti {...successPreset} {...props} />;
}Later spread props override the preset. force must stay between 0 and 1.
Mount from a Next.js App Router client componentuse-in-next-client-component
'use client';
import { Confetti } from '@neoconfetti/react';
export function PurchaseComplete() {
return (
<div className="purchase-complete">
<Confetti particleCount={90} />
<p>Payment received</p>
</div>
);
}The package performs DOM work in an effect and its compiled module is marked use client. State-driven replay logic also belongs in a client component.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| canvas-confetti | npm | Choose it for an imperative canvas API, directional bursts, gravity controls, and fewer DOM nodes. |
| react-confetti | npm | Choose it for a full-window canvas shower with continuous recycling and more simulation controls. |
| react-confetti-explosion | npm | Choose it when you want the older React component that this project credits as the source of its burst logic. |