mrkeyoor.com_
Sat 08 Aug 21:58 UTC
npmWeb Frontendupdated 08 Aug 2026

@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.

Verdict

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.

API stability3/5The public surface is small and typed: one Confetti component plus particle count, shape, size, class, duration, colors, force, stage size, and cleanup options. That limits accidental complexity, but 1.0.0 is the only npm release, so there is no multi-release record showing how compatibility fixes or deprecations are handled. The unusual class prop and lack of an imperative controller also leave little room to work around behavior without remounting.
Docs3/5The package README documents every public prop with its type, default, and a short example, and it is unusually candid about the two-nodes-per-particle performance cost and update behavior. It does not provide runnable React examples, framework-specific setup, accessibility guidance, browser support, replay patterns, or an API for lifecycle events. Several example links remain TODO comments, so common integration questions must be answered from the source.
Maintenance2/5The repository is not archived and its eight open items are a modest queue, but the evidence is thin: the sole 1.0.0 release was published in September 2024 and the repository's last push was March 2025. The monorepo covers five framework wrappers, which spreads maintenance beyond React, while the React package has no declared peer dependency and no visible compatibility matrix to signal which React releases are tested.
Ecosystem3/5The package recorded 3,810,205 downloads for the measured week and the same engine has wrappers for React, Svelte, Vue, Solid, and vanilla JavaScript. That makes visual behavior portable across several front-end stacks. The surrounding integration ecosystem is still small, though: the repository has 312 stars, the React API exports only the component and two types, and there are no official adapters, presets, or framework examples beyond basic mounting.

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
Skip it if

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

PackageRegistryPick it when
canvas-confettinpmChoose it for an imperative canvas API, directional bursts, gravity controls, and fewer DOM nodes.
react-confettinpmChoose it for a full-window canvas shower with continuous recycling and more simulation controls.
react-confetti-explosionnpmChoose it when you want the older React component that this project credits as the source of its burst logic.