webdevpuneet.com

Confetti Button — Free HTML CSS JS Snippet

Confetti Button · Buttons · Plain HTML, CSS & JS · Live preview

CategoryButtons

Copy & paste

Source Code

<div class="scene">
  <button class="btn" id="btn" onclick="fire()">
    🎉 Celebrate!
  </button>
  <p class="hint">Click to launch confetti</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

80 particles per click from button getBoundingClientRect centre
Polar coordinates: random angle (0-360deg) + distance (120-360px) to dx/dy
CSS custom properties --dx --dy --rot --dur --delay per particle
@keyframes confetti translates and rotates each particle uniquely via CSS vars
+200px gravity offset on --dy pulls all particles downward
animationend listener removes each element — no DOM accumulation
7-colour array cycles via i % colors.length for even distribution
Mix of circles and rectangles via random border-radius toggle
Export as HTML file, React JSX, or React + Tailwind CSS
Live split-pane editor — preview updates as you type

About this UI Snippet

Confetti Button — CSS Custom Property Particles with Polar Coordinate Physics

Screenshot of the Confetti Button snippet rendered live

A confetti burst on click is one of the most satisfying micro-interactions — like the ripple button, 80 coloured particles explode from the button and fall with simulated gravity. Used on purchase confirmations (pair with an animated success checkmark), achievements, and any celebratory moment worth rewarding — see also the full-card confetti celebration.

How particles are created

fire() uses getBoundingClientRect() to find the button centre. It creates 80 div.confetti-piece elements appended to document.body, all starting at the button centre.

CSS custom properties drive physics

Each particle has five inline CSS custom properties: --dx (horizontal offset), --dy (vertical offset + 200px gravity), --rot (random rotation), --dur (duration 0.8–1.6s), --delay (0–0.2s). The @keyframes confetti reads them: transform: translate(var(--dx), var(--dy)) rotate(var(--rot)). Each of 80 identical divs follows a unique trajectory purely from CSS.

Polar coordinate direction

Random angle in radians × distance gives Math.cos(angle)*dist for --dx and Math.sin(angle)*dist + 200 for --dy. The +200 shifts the burst centroid downward, simulating gravity.

Auto cleanup

animationend listener calls el.remove() — no DOM accumulation even with rapid clicking.

The physics simulation

Each confetti particle is created at the click position with random polar coordinates: vx = speed * Math.cos(angle), vy = speed * Math.sin(angle). A gravity constant (gy = 0.15) adds to vy every frame, causing particles to arc downward naturally. Each particle has an alpha value that decrements each frame until it reaches 0, at which point the particle is removed from the array. The result is 80 particles that burst outward, arc downward with gravity, and fade as they travel.

CSS custom properties for trajectories

Each particle div has CSS custom properties (--tx, --ty, --r) set as inline styles at creation time. A CSS @keyframes animation uses these properties for the movement: transform: translate(var(--tx), var(--ty)) rotate(var(--r)). This delegates the heavy per-frame position calculation to CSS instead of JavaScript DOM manipulation, keeping the main thread free during the animation burst.

Preventing multiple bursts

A simple isAnimating boolean prevents overlapping burst cycles. When the button is clicked and isAnimating is true, the click handler returns immediately. The flag resets after all particles have faded — typically 1.2–1.5 seconds after the burst.

Customising the burst

Change PARTICLE_COUNT from 80 to adjust density. Increase gravity (gy) for a quicker arc; decrease for a flatter trajectory. Change the colour generator from random hsl to specific colours: const colours = ["#6366f1","#ec4899","#f59e0b"]; const colour = colours[Math.floor(Math.random()*colours.length)]. For a brand-specific confetti palette, this keeps the burst on-brand while still feeling celebratory.

Step by step

How to Use

  1. 1
    Click the buttonClick "Celebrate!" in the preview to see 80 particles burst from the button centre in all directions with random colours, sizes, and timings.
  2. 2
    Change particle coloursIn the JS panel, update the colors array. The 7 colours cycle via i % colors.length — more colours give a richer burst.
  3. 3
    Change particle countUpdate the 80 in the for loop to more or fewer particles.
  4. 4
    Adjust the spread radiusUpdate the 120 + Math.random() * 240 expression to change the minimum and range of particle distances.
  5. 5
    Change the button styleUpdate the gradient, border-radius, and padding in the CSS panel.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Purchase and subscription confirmations
Fire confetti on the CTA click when a user completes a purchase. The burst makes the transaction feel rewarding and memorable.
Achievement and milestone unlocks
Trigger on level completion, streaks, or profile achievements. The particle burst visually rewards the user action.
Learn CSS custom properties and polar coordinates
Edit the angle calculation and --dx/--dy assignments in the JS panel to understand how polar math creates the radial burst pattern.
Form submission success
Add confetti to the submit button on a contact or signup form. Visual reward for form completion increases perceived quality.
Holiday and campaign CTAs
Adjust particle colours to match a seasonal campaign palette. Orange/gold for harvest, red/green for winter, pink/red for Valentine.
Call fire() from any element
Pass any element to fire() and use its getBoundingClientRect centre as the burst origin. Works on cards, icons, or any clickable element.
Related: File System Access Save Dialog
See the File System Access Save Dialog for a related buttons pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each particle div has inline style properties --dx, --dy, --rot, --dur, --delay set by JS with random values. The CSS @keyframes rule reads these variables: transform: translate(var(--dx), var(--dy)) rotate(var(--rot)). All 80 particles share the same CSS class but follow unique paths.

Adding 200 to --dy shifts the vertical endpoint of every particle downward by 200px. Since all particles end lower than they start regardless of direction, the burst appears to fall with gravity.

Each particle has el.addEventListener("animationend", () => el.remove()). After the keyframe animation completes (0.8-1.6s), the event fires and removes the element from the DOM.

Refactor fire() to accept a button element parameter: fire(btn). Use btn.getBoundingClientRect() inside. Add onclick="fire(this)" to each button.

Yes. Click "JSX" for a React component. Call fire() from the onClick handler. Since particles append to document.body, they work identically in React. For a state-based approach, manage particles as an array and render positioned divs.

Update the for loop condition: for (let i = 0; i < 80; i++). Fewer particles (30-40) are more subtle; more (120-150) are more dramatic but slightly more expensive to animate.