24 / 26
All 26 in 26 CSS Dividers
- 01SVG Wave Section Divider CSS
- 02Diagonal Slanted Section Divider CSS
- 03Gradient Horizontal Rule CSS
- 04Text with Lines on Sides Divider CSS ("OR" Divider)
- 05Curved Arch Section Divider CSS
- 06Zigzag Torn Paper Cut Divider CSS
- 07Blob Organic SVG Divider CSS
- 08Pure CSS Ornamental Divider with Pseudo-Elements
- 09Editorial Article Serif Divider CSS
- 10Neo-Brutalist Heavy Border Divider CSS
- 11Art Deco Geometric Gold Divider CSS
- 12Organic Leaf & Vine SVG Divider CSS
- 13Cyberpunk Neon Glow Divider CSS
- 14Luxury Minimalist Gold Accent HR Line CSS
- 15Data Dashboard Dotted Grid Divider CSS
- 16Wedding Heart Icon Divider CSS
- 17Editorial Thin Double-Line Divider CSS
- 18Medieval Fantasy Star Crest Divider CSS
- 19Subtle Faded Border-Bottom Divider CSS
- 208-Bit Retro Pixelated Divider CSS
- 21Multi-Column Newspaper Rule Divider CSS
- 22Bouncing Animated Dots Divider CSS
- 23Audio Waveform SVG Divider CSS
- 24Confetti & Sparkle SVG Divider CSS
- 25Vertical Flexbox Divider Line CSS
- 26Hand-Drawn Doodle SVG Divider CSS
Confetti & Sparkle SVG Divider CSS
A festive divider that earns its animation: four-point sparkles twinkle on a staggered loop along a gradient rule, and a single button fires a real confetti burst — 24 pieces created, animated with the Web Animations API, and removed on finish, so nothing accumulates in the DOM. Exactly the pattern you want for a holiday sale banner or a checkout success state.
Build interactive UI like this from scratch: 20 HTML, CSS & JavaScript projects
ZTM · JavaScript Web Projects · 30‑day money-back guarantee · Affiliate link
The code
<section class="div-24" aria-label="Confetti and sparkle divider demo">
<div class="div-24__stage">
<p class="div-24__eyebrow">LIMITED · 48 HOURS</p>
<h2 class="div-24__title">Everything Divided<br><span class="div-24__em">by two</span></h2>
<div class="div-24__rule" role="separator" aria-label="Divider">
<span class="div-24__sparkle" style="--div-24-i:0"></span>
<span class="div-24__sparkle div-24__sparkle--lg" style="--div-24-i:1"></span>
<span class="div-24__sparkle" style="--div-24-i:2"></span>
</div>
<button class="div-24__btn" id="div-24-fire" type="button">Celebrate</button>
<span class="div-24__burst" id="div-24-burst" aria-hidden="true"></span>
<p class="div-24__chip">clip-path sparkle · element.animate() · onfinish → remove()</p>
</div>
</section><section class="div-24" aria-label="Confetti and sparkle divider demo">
<div class="div-24__stage">
<p class="div-24__eyebrow">LIMITED · 48 HOURS</p>
<h2 class="div-24__title">Everything Divided<br><span class="div-24__em">by two</span></h2>
<div class="div-24__rule" role="separator" aria-label="Divider">
<span class="div-24__sparkle" style="--div-24-i:0"></span>
<span class="div-24__sparkle div-24__sparkle--lg" style="--div-24-i:1"></span>
<span class="div-24__sparkle" style="--div-24-i:2"></span>
</div>
<button class="div-24__btn" id="div-24-fire" type="button">Celebrate</button>
<span class="div-24__burst" id="div-24-burst" aria-hidden="true"></span>
<p class="div-24__chip">clip-path sparkle · element.animate() · onfinish → remove()</p>
</div>
</section>.div-24 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--div-24-bg);
--div-24-bg: oklch(0.21 0.05 300);
--div-24-ink: oklch(0.97 0.012 300);
--div-24-mut: oklch(0.72 0.04 300);
--div-24-gold: oklch(0.88 0.15 92);
--div-24-acc: oklch(0.74 0.18 350);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--div-24-ink);
}
.div-24 *,
.div-24 *::before,
.div-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.div-24__stage {
position: relative;
min-height: 100svh;
display: grid;
place-content: center;
justify-items: center;
gap: clamp(16px,3vw,26px);
text-align: center;
padding: clamp(28px,6vw,72px) clamp(20px,5vw,56px);
background: radial-gradient(75% 55% at 50% 40%,oklch(0.3 0.1 320/.8),transparent);
}
.div-24__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .28em;
color: var(--div-24-gold);
}
.div-24__title {
font-size: clamp(32px,7vw,68px);
line-height: 1.02;
letter-spacing: -.03em;
font-weight: 750;
text-wrap: balance;
}
.div-24__em {
background: linear-gradient(100deg,var(--div-24-gold),var(--div-24-acc));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-style: italic;
}
.div-24__rule {
width: min(88vw,480px);
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
}
.div-24__rule::before,
.div-24__rule::after {
content: '';
flex: 1 1 0;
height: 1px;
background: linear-gradient(90deg,transparent,var(--div-24-gold));
}
.div-24__rule::after {
background: linear-gradient(90deg,var(--div-24-gold),transparent);
}
.div-24__sparkle {
width: 13px;
height: 13px;
flex: none;
background: var(--div-24-gold);
clip-path: polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);
animation: div-24-twinkle 2.6s ease-in-out infinite;
animation-delay: calc(var(--div-24-i) * -.72s);
filter: drop-shadow(0 0 7px oklch(0.88 0.15 92/.6));
}
.div-24__sparkle--lg {
width: 21px;
height: 21px;
}
@keyframes div-24-twinkle {
0%,
100% {
scale: .6;
opacity: .5;
rotate: 0deg;
}
50% {
scale: 1.15;
opacity: 1;
rotate: 90deg;
}
}
.div-24__btn {
min-height: 48px;
padding: 0 28px;
border: 0;
border-radius: 99px;
background: linear-gradient(110deg,var(--div-24-acc),oklch(0.68 0.2 20));
color: oklch(0.99 0.01 350);
font-family: inherit;
font-size: 15px;
font-weight: 680;
letter-spacing: .01em;
cursor: pointer;
box-shadow: 0 10px 26px oklch(0.6 0.2 350/.4);
transition: transform .18s ease,box-shadow .18s ease;
}
.div-24__btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 32px oklch(0.6 0.2 350/.5);
}
.div-24__btn:active {
transform: translateY(0);
}
.div-24__btn:focus-visible {
outline: 2px solid var(--div-24-gold);
outline-offset: 3px;
}
.div-24__burst {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
pointer-events: none;
overflow: visible;
}
.div-24__piece {
position: absolute;
left: 0;
top: 0;
border-radius: 1px;
will-change: transform,opacity;
}
.div-24__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--div-24-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
max-width: 92vw;
}
@media (prefers-reduced-motion: reduce) {
.div-24__sparkle {
animation: none;
scale: 1;
opacity: 1;
}
}.div-24 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--div-24-bg);
--div-24-bg: oklch(0.21 0.05 300);
--div-24-ink: oklch(0.97 0.012 300);
--div-24-mut: oklch(0.72 0.04 300);
--div-24-gold: oklch(0.88 0.15 92);
--div-24-acc: oklch(0.74 0.18 350);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--div-24-ink);
}
.div-24 *,
.div-24 *::before,
.div-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.div-24__stage {
position: relative;
min-height: 100svh;
display: grid;
place-content: center;
justify-items: center;
gap: clamp(16px,3vw,26px);
text-align: center;
padding: clamp(28px,6vw,72px) clamp(20px,5vw,56px);
background: radial-gradient(75% 55% at 50% 40%,oklch(0.3 0.1 320/.8),transparent);
}
.div-24__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .28em;
color: var(--div-24-gold);
}
.div-24__title {
font-size: clamp(32px,7vw,68px);
line-height: 1.02;
letter-spacing: -.03em;
font-weight: 750;
text-wrap: balance;
}
.div-24__em {
background: linear-gradient(100deg,var(--div-24-gold),var(--div-24-acc));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-style: italic;
}
.div-24__rule {
width: min(88vw,480px);
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
}
.div-24__rule::before,
.div-24__rule::after {
content: '';
flex: 1 1 0;
height: 1px;
background: linear-gradient(90deg,transparent,var(--div-24-gold));
}
.div-24__rule::after {
background: linear-gradient(90deg,var(--div-24-gold),transparent);
}
.div-24__sparkle {
width: 13px;
height: 13px;
flex: none;
background: var(--div-24-gold);
clip-path: polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);
animation: div-24-twinkle 2.6s ease-in-out infinite;
animation-delay: calc(var(--div-24-i) * -.72s);
filter: drop-shadow(0 0 7px oklch(0.88 0.15 92/.6));
}
.div-24__sparkle--lg {
width: 21px;
height: 21px;
}
@keyframes div-24-twinkle {
0%,
100% {
scale: .6;
opacity: .5;
rotate: 0deg;
}
50% {
scale: 1.15;
opacity: 1;
rotate: 90deg;
}
}
.div-24__btn {
min-height: 48px;
padding: 0 28px;
border: 0;
border-radius: 99px;
background: linear-gradient(110deg,var(--div-24-acc),oklch(0.68 0.2 20));
color: oklch(0.99 0.01 350);
font-family: inherit;
font-size: 15px;
font-weight: 680;
letter-spacing: .01em;
cursor: pointer;
box-shadow: 0 10px 26px oklch(0.6 0.2 350/.4);
transition: transform .18s ease,box-shadow .18s ease;
}
.div-24__btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 32px oklch(0.6 0.2 350/.5);
}
.div-24__btn:active {
transform: translateY(0);
}
.div-24__btn:focus-visible {
outline: 2px solid var(--div-24-gold);
outline-offset: 3px;
}
.div-24__burst {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
pointer-events: none;
overflow: visible;
}
.div-24__piece {
position: absolute;
left: 0;
top: 0;
border-radius: 1px;
will-change: transform,opacity;
}
.div-24__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--div-24-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
max-width: 92vw;
}
@media (prefers-reduced-motion: reduce) {
.div-24__sparkle {
animation: none;
scale: 1;
opacity: 1;
}
}(() => {
const btn = document.getElementById('div-24-fire');
const host = document.getElementById('div-24-burst');
if (!btn || !host || btn.dataset.ppWired) return;
btn.dataset.ppWired = '1';
const calm = window.matchMedia('(prefers-reduced-motion: reduce)');
const hues = [92, 350, 20, 300, 200];
btn.addEventListener('click', () => {
if (calm.matches) return;
for (let i = 0; i < 24; i++) {
const p = document.createElement('i');
p.className = 'div-24__piece';
const wide = Math.random() > 0.5;
p.style.cssText = 'width:' + (wide ? 8 : 3) + 'px;height:' + (wide ? 4 : 10) +
'px;background:oklch(0.8 0.18 ' + hues[i % hues.length] + ')';
host.appendChild(p);
const a = (i / 24) * Math.PI * 2 + Math.random() * 0.5;
const d = 90 + Math.random() * 150;
p.animate([
{ translate: '0 0', rotate: '0deg', opacity: 1 },
{ translate: (Math.cos(a) * d).toFixed(0) + 'px ' + (Math.sin(a) * d - 40).toFixed(0) + 'px',
rotate: (Math.random() * 900 - 450).toFixed(0) + 'deg', opacity: 0 }
], { duration: 900 + Math.random() * 600, easing: 'cubic-bezier(.2,.7,.3,1)' })
.onfinish = () => p.remove();
}
});
})();(() => {
const btn = document.getElementById('div-24-fire');
const host = document.getElementById('div-24-burst');
if (!btn || !host || btn.dataset.ppWired) return;
btn.dataset.ppWired = '1';
const calm = window.matchMedia('(prefers-reduced-motion: reduce)');
const hues = [92, 350, 20, 300, 200];
btn.addEventListener('click', () => {
if (calm.matches) return;
for (let i = 0; i < 24; i++) {
const p = document.createElement('i');
p.className = 'div-24__piece';
const wide = Math.random() > 0.5;
p.style.cssText = 'width:' + (wide ? 8 : 3) + 'px;height:' + (wide ? 4 : 10) +
'px;background:oklch(0.8 0.18 ' + hues[i % hues.length] + ')';
host.appendChild(p);
const a = (i / 24) * Math.PI * 2 + Math.random() * 0.5;
const d = 90 + Math.random() * 150;
p.animate([
{ translate: '0 0', rotate: '0deg', opacity: 1 },
{ translate: (Math.cos(a) * d).toFixed(0) + 'px ' + (Math.sin(a) * d - 40).toFixed(0) + 'px',
rotate: (Math.random() * 900 - 450).toFixed(0) + 'deg', opacity: 0 }
], { duration: 900 + Math.random() * 600, easing: 'cubic-bezier(.2,.7,.3,1)' })
.onfinish = () => p.remove();
}
});
})();Here's a working CSS Divider from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Confetti & Sparkle SVG Divider CSS
Source: https://codefronts.com/snippets/css-dividers/festive-dividers/
A festive divider that earns its animation: four-point sparkles twinkle on a staggered loop along a gradient rule, and a single button fires a real confetti burst — 24 pieces created, animated with the Web Animations API, and removed on finish, so nothing accumulates in the DOM. Exactly the pattern you want for a holiday sale banner or a checkout success state.
## HTML
```html
<section class="div-24" aria-label="Confetti and sparkle divider demo">
<div class="div-24__stage">
<p class="div-24__eyebrow">LIMITED · 48 HOURS</p>
<h2 class="div-24__title">Everything Divided<br><span class="div-24__em">by two</span></h2>
<div class="div-24__rule" role="separator" aria-label="Divider">
<span class="div-24__sparkle" style="--div-24-i:0"></span>
<span class="div-24__sparkle div-24__sparkle--lg" style="--div-24-i:1"></span>
<span class="div-24__sparkle" style="--div-24-i:2"></span>
</div>
<button class="div-24__btn" id="div-24-fire" type="button">Celebrate</button>
<span class="div-24__burst" id="div-24-burst" aria-hidden="true"></span>
<p class="div-24__chip">clip-path sparkle · element.animate() · onfinish → remove()</p>
</div>
</section>
```
## CSS
```css
.div-24 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--div-24-bg);
--div-24-bg: oklch(0.21 0.05 300);
--div-24-ink: oklch(0.97 0.012 300);
--div-24-mut: oklch(0.72 0.04 300);
--div-24-gold: oklch(0.88 0.15 92);
--div-24-acc: oklch(0.74 0.18 350);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--div-24-ink);
}
.div-24 *,
.div-24 *::before,
.div-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.div-24__stage {
position: relative;
min-height: 100svh;
display: grid;
place-content: center;
justify-items: center;
gap: clamp(16px,3vw,26px);
text-align: center;
padding: clamp(28px,6vw,72px) clamp(20px,5vw,56px);
background: radial-gradient(75% 55% at 50% 40%,oklch(0.3 0.1 320/.8),transparent);
}
.div-24__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .28em;
color: var(--div-24-gold);
}
.div-24__title {
font-size: clamp(32px,7vw,68px);
line-height: 1.02;
letter-spacing: -.03em;
font-weight: 750;
text-wrap: balance;
}
.div-24__em {
background: linear-gradient(100deg,var(--div-24-gold),var(--div-24-acc));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-style: italic;
}
.div-24__rule {
width: min(88vw,480px);
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
}
.div-24__rule::before,
.div-24__rule::after {
content: '';
flex: 1 1 0;
height: 1px;
background: linear-gradient(90deg,transparent,var(--div-24-gold));
}
.div-24__rule::after {
background: linear-gradient(90deg,var(--div-24-gold),transparent);
}
.div-24__sparkle {
width: 13px;
height: 13px;
flex: none;
background: var(--div-24-gold);
clip-path: polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);
animation: div-24-twinkle 2.6s ease-in-out infinite;
animation-delay: calc(var(--div-24-i) * -.72s);
filter: drop-shadow(0 0 7px oklch(0.88 0.15 92/.6));
}
.div-24__sparkle--lg {
width: 21px;
height: 21px;
}
@keyframes div-24-twinkle {
0%,
100% {
scale: .6;
opacity: .5;
rotate: 0deg;
}
50% {
scale: 1.15;
opacity: 1;
rotate: 90deg;
}
}
.div-24__btn {
min-height: 48px;
padding: 0 28px;
border: 0;
border-radius: 99px;
background: linear-gradient(110deg,var(--div-24-acc),oklch(0.68 0.2 20));
color: oklch(0.99 0.01 350);
font-family: inherit;
font-size: 15px;
font-weight: 680;
letter-spacing: .01em;
cursor: pointer;
box-shadow: 0 10px 26px oklch(0.6 0.2 350/.4);
transition: transform .18s ease,box-shadow .18s ease;
}
.div-24__btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 32px oklch(0.6 0.2 350/.5);
}
.div-24__btn:active {
transform: translateY(0);
}
.div-24__btn:focus-visible {
outline: 2px solid var(--div-24-gold);
outline-offset: 3px;
}
.div-24__burst {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
pointer-events: none;
overflow: visible;
}
.div-24__piece {
position: absolute;
left: 0;
top: 0;
border-radius: 1px;
will-change: transform,opacity;
}
.div-24__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--div-24-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
max-width: 92vw;
}
@media (prefers-reduced-motion: reduce) {
.div-24__sparkle {
animation: none;
scale: 1;
opacity: 1;
}
}
```
## JavaScript
```js
(() => {
const btn = document.getElementById('div-24-fire');
const host = document.getElementById('div-24-burst');
if (!btn || !host || btn.dataset.ppWired) return;
btn.dataset.ppWired = '1';
const calm = window.matchMedia('(prefers-reduced-motion: reduce)');
const hues = [92, 350, 20, 300, 200];
btn.addEventListener('click', () => {
if (calm.matches) return;
for (let i = 0; i < 24; i++) {
const p = document.createElement('i');
p.className = 'div-24__piece';
const wide = Math.random() > 0.5;
p.style.cssText = 'width:' + (wide ? 8 : 3) + 'px;height:' + (wide ? 4 : 10) +
'px;background:oklch(0.8 0.18 ' + hues[i % hues.length] + ')';
host.appendChild(p);
const a = (i / 24) * Math.PI * 2 + Math.random() * 0.5;
const d = 90 + Math.random() * 150;
p.animate([
{ translate: '0 0', rotate: '0deg', opacity: 1 },
{ translate: (Math.cos(a) * d).toFixed(0) + 'px ' + (Math.sin(a) * d - 40).toFixed(0) + 'px',
rotate: (Math.random() * 900 - 450).toFixed(0) + 'deg', opacity: 0 }
], { duration: 900 + Math.random() * 600, easing: 'cubic-bezier(.2,.7,.3,1)' })
.onfinish = () => p.remove();
}
});
})();
```Here's a working CSS Divider from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Confetti & Sparkle SVG Divider CSS
Source: https://codefronts.com/snippets/css-dividers/festive-dividers/
A festive divider that earns its animation: four-point sparkles twinkle on a staggered loop along a gradient rule, and a single button fires a real confetti burst — 24 pieces created, animated with the Web Animations API, and removed on finish, so nothing accumulates in the DOM. Exactly the pattern you want for a holiday sale banner or a checkout success state.
## HTML
```html
<section class="div-24" aria-label="Confetti and sparkle divider demo">
<div class="div-24__stage">
<p class="div-24__eyebrow">LIMITED · 48 HOURS</p>
<h2 class="div-24__title">Everything Divided<br><span class="div-24__em">by two</span></h2>
<div class="div-24__rule" role="separator" aria-label="Divider">
<span class="div-24__sparkle" style="--div-24-i:0"></span>
<span class="div-24__sparkle div-24__sparkle--lg" style="--div-24-i:1"></span>
<span class="div-24__sparkle" style="--div-24-i:2"></span>
</div>
<button class="div-24__btn" id="div-24-fire" type="button">Celebrate</button>
<span class="div-24__burst" id="div-24-burst" aria-hidden="true"></span>
<p class="div-24__chip">clip-path sparkle · element.animate() · onfinish → remove()</p>
</div>
</section>
```
## CSS
```css
.div-24 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--div-24-bg);
--div-24-bg: oklch(0.21 0.05 300);
--div-24-ink: oklch(0.97 0.012 300);
--div-24-mut: oklch(0.72 0.04 300);
--div-24-gold: oklch(0.88 0.15 92);
--div-24-acc: oklch(0.74 0.18 350);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--div-24-ink);
}
.div-24 *,
.div-24 *::before,
.div-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.div-24__stage {
position: relative;
min-height: 100svh;
display: grid;
place-content: center;
justify-items: center;
gap: clamp(16px,3vw,26px);
text-align: center;
padding: clamp(28px,6vw,72px) clamp(20px,5vw,56px);
background: radial-gradient(75% 55% at 50% 40%,oklch(0.3 0.1 320/.8),transparent);
}
.div-24__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .28em;
color: var(--div-24-gold);
}
.div-24__title {
font-size: clamp(32px,7vw,68px);
line-height: 1.02;
letter-spacing: -.03em;
font-weight: 750;
text-wrap: balance;
}
.div-24__em {
background: linear-gradient(100deg,var(--div-24-gold),var(--div-24-acc));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-style: italic;
}
.div-24__rule {
width: min(88vw,480px);
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
}
.div-24__rule::before,
.div-24__rule::after {
content: '';
flex: 1 1 0;
height: 1px;
background: linear-gradient(90deg,transparent,var(--div-24-gold));
}
.div-24__rule::after {
background: linear-gradient(90deg,var(--div-24-gold),transparent);
}
.div-24__sparkle {
width: 13px;
height: 13px;
flex: none;
background: var(--div-24-gold);
clip-path: polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);
animation: div-24-twinkle 2.6s ease-in-out infinite;
animation-delay: calc(var(--div-24-i) * -.72s);
filter: drop-shadow(0 0 7px oklch(0.88 0.15 92/.6));
}
.div-24__sparkle--lg {
width: 21px;
height: 21px;
}
@keyframes div-24-twinkle {
0%,
100% {
scale: .6;
opacity: .5;
rotate: 0deg;
}
50% {
scale: 1.15;
opacity: 1;
rotate: 90deg;
}
}
.div-24__btn {
min-height: 48px;
padding: 0 28px;
border: 0;
border-radius: 99px;
background: linear-gradient(110deg,var(--div-24-acc),oklch(0.68 0.2 20));
color: oklch(0.99 0.01 350);
font-family: inherit;
font-size: 15px;
font-weight: 680;
letter-spacing: .01em;
cursor: pointer;
box-shadow: 0 10px 26px oklch(0.6 0.2 350/.4);
transition: transform .18s ease,box-shadow .18s ease;
}
.div-24__btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 32px oklch(0.6 0.2 350/.5);
}
.div-24__btn:active {
transform: translateY(0);
}
.div-24__btn:focus-visible {
outline: 2px solid var(--div-24-gold);
outline-offset: 3px;
}
.div-24__burst {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
pointer-events: none;
overflow: visible;
}
.div-24__piece {
position: absolute;
left: 0;
top: 0;
border-radius: 1px;
will-change: transform,opacity;
}
.div-24__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--div-24-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
max-width: 92vw;
}
@media (prefers-reduced-motion: reduce) {
.div-24__sparkle {
animation: none;
scale: 1;
opacity: 1;
}
}
```
## JavaScript
```js
(() => {
const btn = document.getElementById('div-24-fire');
const host = document.getElementById('div-24-burst');
if (!btn || !host || btn.dataset.ppWired) return;
btn.dataset.ppWired = '1';
const calm = window.matchMedia('(prefers-reduced-motion: reduce)');
const hues = [92, 350, 20, 300, 200];
btn.addEventListener('click', () => {
if (calm.matches) return;
for (let i = 0; i < 24; i++) {
const p = document.createElement('i');
p.className = 'div-24__piece';
const wide = Math.random() > 0.5;
p.style.cssText = 'width:' + (wide ? 8 : 3) + 'px;height:' + (wide ? 4 : 10) +
'px;background:oklch(0.8 0.18 ' + hues[i % hues.length] + ')';
host.appendChild(p);
const a = (i / 24) * Math.PI * 2 + Math.random() * 0.5;
const d = 90 + Math.random() * 150;
p.animate([
{ translate: '0 0', rotate: '0deg', opacity: 1 },
{ translate: (Math.cos(a) * d).toFixed(0) + 'px ' + (Math.sin(a) * d - 40).toFixed(0) + 'px',
rotate: (Math.random() * 900 - 450).toFixed(0) + 'deg', opacity: 0 }
], { duration: 900 + Math.random() * 600, easing: 'cubic-bezier(.2,.7,.3,1)' })
.onfinish = () => p.remove();
}
});
})();
```How this works
The sparkle is one element with a concave clip:
.sparkle{ width:14px; aspect-ratio:1; background:var(--gold);
clip-path:polygon(50% 0,58% 42%,100% 50%,58% 58%,
50% 100%,42% 58%,0 50%,42% 42%); }The tight 42/58 inner points are what make it read as a glint rather than a star — light, not heraldry. Twinkling animates scale and rotate together with different negative delays per sparkle, so no two are ever at the same phase.
The burst uses the Web Animations API rather than CSS, because the trajectories must be random per piece:
piece.animate(
[{ translate:'0 0', rotate:'0deg', opacity:1 },
{ translate:x+'px '+y+'px', rotate:r+'deg', opacity:0 }],
{ duration:900+Math.random()*500, easing:'cubic-bezier(.2,.7,.3,1)' }
).onfinish = () => piece.remove();onfinish → remove() is the non-negotiable part: without it a user who mashes the button leaves hundreds of dead nodes in the document. Generating the pieces on demand also means zero markup cost when nobody clicks.
The easing is deliberately front-loaded — fast out, slow settle — which is how thrown paper actually behaves; a linear burst looks like a screensaver.
Make it yours
- Piece count: 24 is festive, 60 is a celebration, 120 is a performance problem on a mid-range phone. Test on real hardware.
- Shapes: mix rectangles, circles and thin strips (
width:3px;height:9px) — real confetti is never uniform. - Gravity: add a third keyframe with a positive y and an
easing:'cubic-bezier(.4,0,1,1)'tail for pieces that arc and fall rather than fly straight. - Auto-fire once: call the burst inside an IntersectionObserver so the divider celebrates when it scrolls into view — but only the first time.
Gotchas — read before shipping
- Never leave animated nodes in the DOM. Always remove on finish, and cap concurrent bursts if the button can be spammed.
- Confetti is decorative motion by definition — check
matchMedia('(prefers-reduced-motion: reduce)')in JS and skip the burst entirely, not just slow it down. - The burst container needs
pointer-events:noneandoverflow:visible, or pieces get clipped by the divider box or block the button. - Animating many elements at once is fine only if you stick to transform and opacity; adding box-shadow or filter to 60 pieces will drop frames.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
Web Animations API element.animate(): Chrome 36, Safari 13.1, Firefox 48. The individual translate/rotate properties in keyframes need Chrome 104 / Safari 14.1 / Firefox 72.