A lightweight, performant confetti animation library for the web. Zero dependencies, TypeScript-first, and smooth on any refresh rate.
- Zero dependencies, about 5 kB gzipped
- TypeScript support with full type definitions, for ESM and CommonJS
- Paper-like pieces that tumble in 3D, catch the light and sway as they float down
- Time-based animation that looks the same at 60 Hz and 120 Hz, sharp on high-density screens
- Accessibility support (respects
prefers-reduced-motion) - Safe to import and call during server rendering
- Seven preset effects plus a continuous stream
- Framework wrappers for React, Vue, Svelte, Solid, and Web Components
| Package | Version | Description |
|---|---|---|
| @konfetti-js/core | Core library | |
| @konfetti-js/react | React wrapper | |
| @konfetti-js/vue | Vue wrapper | |
| @konfetti-js/svelte | Svelte wrapper | |
| @konfetti-js/solid | Solid wrapper | |
| @konfetti-js/web-components | Web Components |
All packages are released together and share one version number.
Upgrading from the
konfetti.jspackage? See the migration guide.
npm install @konfetti-js/core
# or
pnpm add @konfetti-js/coreEach wrapper installs @konfetti-js/core for you. If you also add core yourself (to import from it directly), keep it on the same version as the wrapper.
pnpm add @konfetti-js/react
pnpm add @konfetti-js/vue
pnpm add @konfetti-js/svelte
pnpm add @konfetti-js/solid
pnpm add @konfetti-js/web-components<script src="https://cdn.jsdelivr.net/npm/@konfetti-js/core@0.4/dist/konfetti.min.js"></script>
<script>
konfetti.fire({ particleCount: 100 });
</script>Or as an ES module:
<script type="module">
import { fireworks } from 'https://cdn.jsdelivr.net/npm/@konfetti-js/core@0.4/+esm';
fireworks();
</script>import { fire, cannon, fireFromElement } from '@konfetti-js/core';
// Fire confetti!
fire();
// Use a preset, and override any of its options
cannon({ colors: ['#ff48b0', '#0078bf'] });
// Fire from the button that was clicked
button.addEventListener('click', () => fireFromElement(button, { preset: 'explosion' }));import { useState } from 'react';
import { useKonfetti, Konfetti } from '@konfetti-js/react';
// Hook usage
function SaveButton() {
const { cannon } = useKonfetti();
return <button onClick={() => cannon()}>Save</button>;
}
// Component usage: fires each time `fire` turns true
function Celebration() {
const [done, setDone] = useState(false);
return (
<>
<button onClick={() => setDone(true)}>Finish</button>
<Konfetti fire={done} preset="fireworks" onFired={() => setDone(false)} />
</>
);
}<script setup>
import { useKonfetti, vKonfetti } from '@konfetti-js/vue';
const { fire } = useKonfetti();
</script>
<template>
<!-- Composable -->
<button @click="fire({ particleCount: 100 })">Celebrate!</button>
<!-- Directive: fires from the element on click -->
<button v-konfetti>Click me!</button>
<button v-konfetti="{ preset: 'fireworks' }">Fireworks!</button>
</template><script>
import { konfettiAction, createKonfetti } from '@konfetti-js/svelte';
const { fire } = createKonfetti();
</script>
<!-- Action: fires from the element on click -->
<button use:konfettiAction>Click me!</button>
<button use:konfettiAction={{ preset: 'fireworks' }}>Fireworks!</button>
<!-- Functions -->
<button onclick={() => fire()}>Celebrate!</button>import { createSignal } from 'solid-js';
import { useKonfetti, Konfetti } from '@konfetti-js/solid';
// Hook usage
function SaveButton() {
const { cannon } = useKonfetti();
return <button onClick={() => cannon()}>Save</button>;
}
// Component usage
function Celebration() {
const [done, setDone] = createSignal(false);
return (
<>
<button onClick={() => setDone(true)}>Finish</button>
<Konfetti fire={done()} preset="fireworks" onFired={() => setDone(false)} />
</>
);
}<script type="module">
import { defineAllElements } from '@konfetti-js/web-components';
defineAllElements();
</script>
<!-- Fires from itself when clicked -->
<konfetti-trigger preset="fireworks">
<button>Click me!</button>
</konfetti-trigger>
<!-- Programmatic control -->
<konfetti-burst id="burst" preset="cannon" particle-count="150"></konfetti-burst>
<script>
document.getElementById('burst').fire();
</script>Attributes use kebab-case option names (particle-count, start-velocity, origin-x, z-index, comma-separated colors and shapes). <konfetti-trigger trigger="mouseenter"> changes the event. Both elements dispatch a konfetti-fired event.
Fires one burst on a full-screen canvas that is created on first use. The canvas ignores pointer events and is hidden from assistive technology.
| Option | Type | Default | Description |
|---|---|---|---|
particleCount |
number | 50 | Number of particles to emit |
angle |
number | 90 | Launch angle in degrees (90 = up) |
spread |
number | 45 | Spread angle in degrees |
startVelocity |
number | 45 | Initial velocity |
decay |
number | 0.9 | Air resistance: share of speed kept per frame (0-1) |
gravity |
number | 1 | Gravity strength |
drift |
number | 0 | Wind in px per frame (negative blows left) |
ticks |
number | 600 | Longest lifetime in frames at 60 fps; pieces usually leave the screen first |
origin |
{x, y} | {0.5, 0.5} | Spawn point (0-1 of the viewport); a missing axis keeps the preset's value |
colors |
string[] | festive | Any CSS colors |
shapes |
string[] | ['circle', 'square'] | Particle shapes |
scalar |
number | 1 | Size multiplier |
zIndex |
number | 100 | Canvas z-index |
disableForReducedMotion |
boolean | true | Respect user motion preference |
Invalid values (for example NaN) fall back to the defaults.
import { cannon, explosion, fireworks, rain, snow, sideCannons, pride, continuous, firePreset } from '@konfetti-js/core';
cannon(); // Burst from bottom
explosion(); // 360 degree burst
fireworks(); // Three bursts across the top half
rain(); // Falling particles
snow(); // Gentle snowfall
sideCannons(); // Burst from both sides
pride(); // Rainbow colors
firePreset('snow', { particleCount: 60 }); // Pick a preset by name
const stop = continuous({ particleCount: 8 }, 200); // A small burst every 200 ms
stop();Every preset takes the same options as fire; anything you pass overrides the preset's values. PRESET_NAMES lists the names and isPresetName(value) checks one.
Fires from the center of an element. Pass preset in the options to fire a preset: single bursts (cannon, explosion, pride) start at the element, while screen-wide presets (fireworks, rain, snow, sideCannons) keep their positions. An origin you pass yourself always wins. originFromElement(element) returns the origin on its own.
reset() clears all particles and cancels scheduled bursts and streams. destroy() also removes the full-screen canvas and its listeners; the next fire() creates a new one.
Draws into your own canvas instead of the full-screen one. Returns a fire function with preset(), continuous(), reset() and destroy() methods. Pass { resize: false } to stop it from resizing with the window.
import { create } from '@konfetti-js/core';
const burst = create(document.querySelector('#card-canvas'));
burst({ particleCount: 80 });
burst.preset('pride');
burst.destroy();For full control there is also the Konfetti class, with fire, preset, continuous, pause, resume, reset and destroy.
- Pieces behave like paper: they tumble in 3D, are lit from the upper left, slow down quickly after the burst and then sway down slowly. They disappear once they fall out of view.
- Animation is time-based: speed and lifetime match on 60 Hz and 120 Hz displays.
- The canvas renders at the screen's pixel density.
- Animation pauses while the tab is hidden, and bursts fired while hidden are skipped.
- On the server every function does nothing, so importing and calling them during SSR is safe.
- Typed-array storage: all particle data lives in one
Float32Array, so bursts don't allocate objects - Cached shades: each color's light and dark tones are computed once, not per frame
- Direct transforms: each piece's 3D orientation is drawn with a single
setTransform, withoutsave/restore - Visibility API: no frames are scheduled while the tab is hidden
- Chrome 80+
- Firefox 75+
- Safari 13.1+
- Edge 80+
By default, the library respects the user's prefers-reduced-motion setting. When enabled, no animations play unless you pass disableForReducedMotion: false.
pnpm install # Install dependencies
pnpm build # Build all packages
pnpm test # Run the tests
pnpm check # Format and lint (Biome)
pnpm typecheck # Type check
pnpm check:packages # publint, attw and require/import smoke tests (after build)
pnpm site:build # Build the demo site into site/ (serve it with any static server)Releases use changesets: add one with pnpm changeset, and merging the generated "Version Packages" pull request publishes to npm.
MIT