letter confetti burst
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
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
| Option | Default | Description |
|---|---|---|
| text | element text | The word/phrase whose letters become confetti. Whitespace is stripped; letters cycle through the pieces. Defaults to the bound element’s text. |
| particleCount | 70 | How many letter-particles to emit. |
| angle | 90 | Launch direction in degrees — 90 points straight up, 0 points right. |
| spread | 62 | Angular spread of the burst in degrees; wider fans out more. |
| startVelocity | 34 | Initial launch speed (px/frame); higher throws further. |
| decay | 0.9 | Per-frame velocity retention (air resistance). Closer to 1 = floatier. |
| gravity | 1.1 | Downward pull per frame; higher = pieces fall faster. |
| drift | 0 | Constant horizontal bias (a breeze); negative drifts left. |
| ticks | 200 | Particle lifetime in frames before it fades out. |
| scalar | 1 | Letter-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. |
| colors | festive palette | Palette 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. |
| flat | false | Disable the 3D paper-flip tumble (letters stay flat as they spin). |
| disableForReducedMotion | true | Skip the burst entirely when the user prefers reduced motion. |
| trigger | 'click' | React-only: 'click' | 'mount' | 'inView' | 'manual'. When the binding fires. |