letter confetti burst


npm ↗
GitHub ↗
TypeScriptZero dependenciesReact + Vanilla JS

A confetti cannon where every piece is a real letter of your own text — not a paper rectangle on a canvas, but a DOM letter-span that arcs out, tumbles through 3D, and falls. The physics are canvas-confetti’s, so it feels familiar; the letters carry variable-font weight jitter, so the burst reads as type. Click to fire.

Live demo — click the word, or hit Fire

Hooray

Edit the word above · click it or hit the button to burst its letters

How it works

Real letters, real DOM

Each particle is a <span> holding one letter of your text, cycled through the burst. They live in a single fixed, pointer-events: none layer appended to <body> — so the confetti floats above everything without intercepting clicks, and every piece renders with your real (variable) font.

Cannon kinematics

Ported from canvas-confetti: each piece launches with randomised velocity inside the spread cone, loses speed to per-frame decay, and is pulled down by gravity. A scaleY(cos(tilt)) term gives the paper flip-through-3D tumble. One requestAnimationFrame loop drives every live burst and retires spent pieces.

Accessibility & reduced motion

The confetti layer is aria-hidden — it’s decorative and never announced. When prefers-reduced-motion: reduce is set, bursts are skipped entirely by default (opt back in with disableForReducedMotion: false). The demo above honours the preference too.

Usage

Drop-in component — click to burst its own text

import { ConfettiText } from '@liiift-studio/confettitext'

<ConfettiText as="h1" particleCount={120} spread={80}>
  Congrats!
</ConfettiText>

Hook — imperative fire()

import { useConfettiText } from '@liiift-studio/confettitext'

const { ref, fire } = useConfettiText({ particleCount: 90 })
<h1 ref={ref}>You did it</h1>
<button onClick={() => fire()}>Celebrate</button>

Vanilla JS

import { confettiText, attachConfettiText, clearConfettiText } from '@liiift-studio/confettitext'

// One-off burst from the viewport centre:
confettiText({ text: 'Hooray', particleCount: 120, spread: 90 })

// Or make an element burst its own text on click:
const detach = attachConfettiText(document.querySelector('h1'))

// Cancel everything in flight:
clearConfettiText()

Options

ConfettiText options reference
OptionDefaultDescription
textelement textThe word/phrase whose letters become confetti. Whitespace is stripped; letters cycle through the pieces. Defaults to the bound element’s text.
particleCount70How many letter-particles to emit.
angle90Launch direction in degrees — 90 points straight up, 0 points right.
spread62Angular spread of the burst in degrees; wider fans out more.
startVelocity34Initial launch speed (px/frame); higher throws further.
decay0.9Per-frame velocity retention (air resistance). Closer to 1 = floatier.
gravity1.1Downward pull per frame; higher = pieces fall faster.
drift0Constant horizontal bias (a breeze); negative drifts left.
ticks200Particle lifetime in frames before it fades out.
scalar1Letter-size multiplier on the base ~18px particle size.
origin{ x: 0.5, y: 0.5 }Burst origin as viewport fractions. The React bindings set this from the element’s position automatically.
colorsfestive palettePalette the letters are randomly coloured from. Pass null to leave them currentColor (monochrome).
weightRange[400, 700]Variable-font wght jitter — each piece gets a random weight in range. Pass null to disable.
flatfalseDisable the 3D paper-flip tumble (letters stay flat as they spin).
disableForReducedMotiontrueSkip the burst entirely when the user prefers reduced motion.
trigger'click'React-only: 'click' | 'mount' | 'inView' | 'manual'. When the binding fires.