Confetti Button
Button that fires ~100 coloured confetti on click: particles burst beyond the button edges, fall under gravity and bounce, computed on a JS canvas.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →
3 usage examples



How it works
The .fx-0370-root frame holds a canvas positioned with overflow (position: absolute; top: -220px; left: -180px) and a button. The canvas is resized in JavaScript to cover a large area around the button without enlarging the DOM: its width and height are computed from devicePixelRatio to stay sharp on Retina screens. The canvas carries aria-hidden="true"; the confetti are purely decorative.
When the button is clicked, burst() creates ~100 particle objects with random position, velocity, colour and rotation. Each requestAnimationFrame frame clears the canvas, applies gravity (vy += 0.4 px/frame), a floor bounce with damping (vy *= −0.5), and slight horizontal friction. Particles disappear when their opacity reaches zero (it decreases by 0.015 per frame). The loop stops when all particles have gone. An IntersectionObserver pauses the loop when the component leaves the screen.
Under prefers-reduced-motion: reduce, burst() creates no particles; the button remains functional but without animation. The button itself has a visible :focus-visible outline and can be activated by keyboard.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, no animated confetti is created. The button still gives visual feedback: its background briefly turns green for 1.2 s to confirm the click. No particles are drawn. - Decorative canvas: the canvas carries
aria-hidden="true"; the confetti convey no information and are ignored by assistive technologies. - Native button: the
<button>element is focusable by Tab, activatable by Enter or Space, and correctly announced by screen readers. Its label can be freely customised. - No information reserved to the visual: the confetti rain is a visual celebration; it does not gate access to a feature. The business action (validation, sign-up…) must be confirmed by another means (text message, confirmation page).
Browser compatibility
2D canvas + 2.8 KB of JavaScript, no dependency. Confetti are computed in pure JS, with no animation library. IntersectionObserver is available since Chrome 51, Firefox 55 and Safari 12.1; without it, the loop also runs off screen. devicePixelRatio is available in all modern browsers. Measured in Chromium 151, Firefox 153 and WebKit 26: burst, gravity and bounce identical in all three.
Without JavaScript, the canvas stays empty and the button renders without effect. Without IntersectionObserver (browsers before 2018), the loop also runs off screen — negligible CPU impact since the loop stops naturally when all particles have gone.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0370-root">
<div class="mi-confetti-scene">
<div class="mi-confetti-wrap">
<canvas class="mi-confetti-canvas" aria-hidden="true"></canvas>
<button class="mi-confetti-btn">Celebrate!</button>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Confetti colours (JS — colors array) |
festive multi-colour palette (red, orange, yellow, green, blue, purple, pink) | Replace with your brand colours. Fewer colours gives a more subtle look; a single colour creates a monochrome effect. |
Number of confetti (JS — count = 100) |
100 particles | Lower to 50 for a lighter, less CPU-intensive effect; raise to 200 for a denser burst. Beyond 300, the performance impact becomes noticeable on mobile. |
Gravity (JS — vy += 0.4) |
0.4 px/frame | Lower to 0.2 for slowly floating confetti; raise to 0.8 for a fast fall. The value is in pixels per frame — on a 120 Hz screen the confetti fall twice as fast. |
Button label (HTML — content of .mi-confetti-btn) |
"Celebrate!" | Replace with "Enter now!", "Done", "Submit" or any contextual label. The button sizes itself automatically. |
Confetti size (JS — particle w and h) |
width 6-10 px, height 3-6 px (random) | Raise the max values for larger, more visible pieces; lower them for a "dust" effect. |
FAQ
absolute with top: -220px; left: -180px and deliberately overflows neighbouring elements. That is the intended effect: confetti invade the space around the button. Make sure the component's direct parent does not have overflow: hidden.burst(canvas, ctx) function directly within the instance's scope. To access it from the outside, add root.dataset.burst = 'ready' in the JS and assign the reference on creation so that root._burst() can be called externally.requestAnimationFrame loop stops and the CPU is no longer used.