Skip to content

ExpressiveCSS

Browse documentation

Confetti

Bursts of theme-colored confetti for a moment worth celebrating.

View as Markdown

Confetti marks a moment worth celebrating, such as a finished order or a completed goal.

Give a button the confetti class, and each click bursts confetti from its center over the whole page. The pieces take the theme's primary, secondary and tertiary colors and their containers. The component draws them on a canvas that lets clicks through and removes the canvas when the last piece fades. AutoInit() starts every .confetti.

<button type="button" class="filled confetti">Celebrate</button>

On a canvas

A <canvas class="confetti" aria-hidden="true"> covers its nearest positioned ancestor and bursts inside it once when it starts. To burst again, call fire() on its instance. To wait for the first call instead, set manualStart. A small canvas needs a lower startVelocity to keep the pieces in view.

Confetti

<div style="position: relative; height: 320px">
  <canvas class="confetti no-autoinit" aria-hidden="true"></canvas>
</div>

<script>
const canvas = document.querySelector('canvas.confetti');
const confetti = Expressive.Confetti.init(canvas, { startVelocity: 30, origin: { y: 0.6 } });
button.addEventListener('click', () => confetti.fire());
</script>

From script

Expressive.Confetti.fire(options) bursts over the page with no markup, for example when a request succeeds. It returns a promise that resolves when the last piece is gone.

await saveOrder();
Expressive.Confetti.fire({ particleCount: 100, spread: 70, origin: { y: 0.6 } });

Examples

Combine several calls for bigger effects. origin is a fraction of the viewport, so { x: 0 } is the left edge.

Random direction

Expressive.Confetti.fire({
  angle: 55 + Math.random() * 70,
  spread: 50 + Math.random() * 20,
  particleCount: 50 + Math.floor(Math.random() * 50),
  origin: { y: 0.6 }
});

Fireworks

const end = Date.now() + 5000;
const timer = setInterval(() => {
  const left = end - Date.now();
  if (left <= 0) return clearInterval(timer);
  const burst = { spread: 360, startVelocity: 30, ticks: 60, particleCount: 50 * left / 5000 };
  Expressive.Confetti.fire({ ...burst, origin: { x: 0.1 + Math.random() * 0.2, y: Math.random() - 0.2 } });
  Expressive.Confetti.fire({ ...burst, origin: { x: 0.7 + Math.random() * 0.2, y: Math.random() - 0.2 } });
}, 250);

Side cannons

const end = Date.now() + 3000;
const colors = ['#a786ff', '#fd8bbc', '#eca184', '#f8deb1'];
(function frame() {
  if (Date.now() > end) return;
  Expressive.Confetti.fire({ particleCount: 2, angle: 60, spread: 55, startVelocity: 60, origin: { x: 0, y: 0.5 }, colors });
  Expressive.Confetti.fire({ particleCount: 2, angle: 120, spread: 55, startVelocity: 60, origin: { x: 1, y: 0.5 }, colors });
  requestAnimationFrame(frame);
})();

Stars

const stars = {
  spread: 360, ticks: 50, gravity: 0, decay: 0.94, startVelocity: 30,
  shapes: ['star'], colors: ['#ffe400', '#ffbd00', '#e89400', '#ffca6c', '#fdffb8']
};
for (const delay of [0, 100, 200]) {
  setTimeout(() => {
    Expressive.Confetti.fire({ ...stars, particleCount: 40 });
    Expressive.Confetti.fire({ ...stars, particleCount: 10, shapes: ['circle'], scalar: 0.75 });
  }, delay);
}

Accessibility

  • Confetti is decoration. Say what happened in text as well, for example in a snackbar, and keep bursts for rare moments.
  • Mark an authored canvas aria-hidden="true". The page canvas hides itself.
  • The component draws nothing when the reader prefers reduced motion, and fire() resolves at once.
  • The page canvas is a manual popover with pointer events off, so it draws above an open modal dialog without blocking clicks or moving focus.

Options

Name Type Default Description
particleCountNumber50Pieces per burst.
angleNumber90Launch direction in degrees; 90 is straight up and 0 is right.
spreadNumber45Degrees the pieces fan out from the angle.
startVelocityNumber45Launch speed in pixels per frame.
decayNumber0.9Share of its speed a piece keeps each frame.
gravityNumber1Pull toward the bottom; 0 floats.
driftNumber0Sideways push per frame; negative drifts left.
ticksNumber200Frames a piece lives while it fades, counted at 60 per second.
originObject{ x: 0.5, y: 0.5 }Launch point as fractions of the canvas from 0 to 1; a missing side is 0.5. A button launches from its own center instead.
colorsArray[]CSS colors. Empty uses the theme colors.
shapesArray['square', 'circle']Any of 'square', 'circle' and 'star'.
scalarNumber1Piece size multiplier.
manualStartBooleanfalseOn a canvas, wait for fire() instead of bursting at init.

Methods

Expressive.Confetti.fire(options);   // bursts over the page; resolves when it ends

const confetti = Expressive.Confetti.getInstance(element);
confetti.fire({ spread: 90 });       // this canvas, or from this button's center
confetti.destroy();                  // removes the click handler, or clears the canvas

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow