How to add a confetti button to your website with canvas-confetti
The quickest way to add a confetti button to a website is the open-source canvas-confetti library. You load it from npm or a single CDN script tag, attach a click handler to a button, and call confetti() with a handful of options. This guide shows the minimal version, the options worth changing, a React version, and how to respect users who prefer reduced motion. Every setting below is the one Pop the Confetti uses, so you can click the live demo and see what you are building.
Install canvas-confetti
canvas-confetti is published on npm under the name canvas-confetti and is maintained by catdad on GitHub. The README gives two ways to load it. With a bundler, install it and import the default export.
npm install --save canvas-confettiimport confetti from "canvas-confetti";Without a bundler, add one script tag from jsDelivr. This is the exact tag from the README, pinned to version 1.9.4, which is also the latest version shown on jsDelivr at the time of writing. It exposes a global confetti function.
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js"></script>The minimal confetti button
Here is a complete page. One button, one script tag, one click handler. Calling confetti() with no arguments uses the library defaults, which are 50 particles, an angle of 90 degrees, a spread of 45, and an origin at the center of the screen.
<button id="pop">Pop it</button>
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js"></script>
<script src="pop.js"></script>document.getElementById("pop").addEventListener("click", () => {
confetti();
});That is a working confetti button. Everything after this point is tuning.
The options that matter
canvas-confetti accepts an options object. Five options do most of the work. The table lists each one, the library default from the README, and the value Pop the Confetti uses on every click.
| Option | What it controls | Library default | Pop the Confetti |
|---|---|---|---|
| particleCount | How many pieces fire per call | 50 | Random 50 to 300 |
| angle | Launch direction in degrees, 90 is straight up | 90 | Random 55 to 125 |
| spread | How wide the cone is, in degrees | 45 | Random 50 to 100 |
| origin | Where the burst starts, as a fraction of the page | { x: 0.5, y: 0.5 } | { y: 0.48 } |
| colors | Array of hex strings | Library palette | Five fixed hex colors |
Randomising the count, angle, and spread is what keeps each pop looking different. Here is the exact call the site makes, with a small helper for the random ranges.
const randomInRange = (min, max) => Math.random() * (max - min) + min;
function popTheConfetti() {
confetti({
particleCount: Math.floor(randomInRange(50, 300)),
angle: randomInRange(55, 125),
spread: randomInRange(50, 100),
origin: { y: 0.48 },
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
});
}A y origin of 0.48 starts the burst just above the vertical middle of the viewport, which is where the big button sits on the site. Leaving x unset keeps it centered. The five colors are a purple, a sky blue, a lime green, a pink, and a yellow. Try them on the live confetti button before you copy them.
A React confetti button
canvas-confetti has no React dependency, so a React version is just the same call inside an onClick. Import the library, define a handler, and render a button.
import confetti from "canvas-confetti";
export function ConfettiButton() {
const pop = () =>
confetti({
particleCount: 120,
angle: 90,
spread: 70,
origin: { y: 0.48 },
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
});
return <button onClick={pop}>Pop it</button>;
}The library creates and manages its own canvas element the first time you call it, so there is nothing to mount, no ref to hold, and no cleanup in a useEffect. If you want to count clicks or measure how fast someone is pressing, keep that state in React and leave the drawing to the library. The stats explainer describes what Pop the Confetti measures on each click if you want ideas.
Reduced motion and accessibility
Some people set their operating system to minimise motion, often because animation makes them dizzy or unwell. MDN documents the prefers-reduced-motion media feature for exactly this case. It has two values, no-preference and reduce, and MDN notes that animations such as scaling or panning large objects can be vestibular motion triggers. Hundreds of tumbling particles count.
canvas-confetti ships a switch for this. Set disableForReducedMotion to true and the library skips the animation entirely for users who prefer reduced motion. The README says the default is false and that the maintainer is considering changing that in a future major release, so set it yourself.
confetti({ particleCount: 120, disableForReducedMotion: true });If you also want to swap in a quieter effect rather than nothing, check the preference in JavaScript with matchMedia. The matches property is true when the document meets the query.
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduceMotion) confetti();Two more habits help. Use a real button element so keyboard users can press it with Space or Enter, and do not put any information in the confetti itself. It is decoration. A screen reader user should get the same confirmation in text.
Performance notes
canvas-confetti draws every particle onto a single canvas element rather than creating a DOM node per piece. That is the main reason it stays smooth with 300 pieces on a phone. A DOM-based approach would create and animate hundreds of elements per click, each one taking part in layout and style calculation.
- One canvas, not one element per particle. The library adds the canvas the first time you call confetti() and reuses it.
- For heavy pages, the README describes a useWorker option that moves the drawing to a web worker. Control of the canvas is transferred to the worker, so you cannot draw on that canvas from the main thread afterwards.
- Keep particleCount sensible. Pop the Confetti tops out at 300 per click and rapid clickers fire several bursts a second without trouble.
- Do not create a new canvas per click. Call the same confetti function each time and let it manage its own canvas.
If you want a sense of how far you can push it, open the confetti button and try the click streak challenge. Each click in a streak fires a full burst and the page stays responsive.
Frequently asked questions
Do I need a framework to use canvas-confetti? No. One script tag and one click listener is enough. React, Vue, and Svelte all work the same way because the library only needs a function call.
Does confetti() need a canvas element in my HTML? No. The library creates a full-page canvas on first use. You only need your own canvas if you want to confine the confetti to one area, which is what the confetti.create function in the README is for.
Why does my confetti fire from the wrong place? Origin is a fraction of the page, not pixels. { y: 0.48 } means 48 percent of the way down. Values above 1 or below 0 start off screen.
Can I use my own colors? Yes. Pass an array of hex strings as colors. The README says colors are accepted in HEX format, and Pop the Confetti uses five of them.