14 / 51
All 51 in 51 Tailwind CSS Buttons
- 01Tailwind Submit Button (Form Primary)
- 02Tailwind Primary and Secondary Button Pair
- 03Tailwind Ghost / Outline Button
- 04Tailwind Icon-Only Button with Accessible Tooltip
- 05Tailwind Gradient CTA Button
- 06Tailwind Loading Spinner Button (State Transition)
- 07Tailwind Disabled Button State (Full State Reference)
- 08Tailwind Button Group Component (Segmented Radio-Like)
- 09Tailwind Add to Cart Button
- 10Tailwind Download Progress Button
- 11Tailwind Like Heart Button
- 12Tailwind Confirm Delete Button
- 13Tailwind Play Pause Button
- 14Tailwind Subscribe Confetti Button
- 15Tailwind Magnetic Mercury Ripple Button
- 16Tailwind Brutalist Glitch Button
- 17Tailwind Neon Plasma Burst Button
- 18Tailwind Clay Press Button
- 19Tailwind Editorial Ink Spread Button
- 20Tailwind Particle Shard Burst Button
- 21Tailwind Brushed Brass Plate Button
- 22Tailwind Washi Paper Crane Button
- 23Tailwind Frosted Ice Cube Button
- 24Tailwind Vintage Typewriter Key Button
- 25Tailwind Lacquered Walnut Wood Button
- 26Tailwind Pharma Gel Capsule Button
- 27Tailwind Aged Copper Coin Button
- 28Tailwind Origami Fortune Cookie Button
- 29Tailwind Holographic Foil Sticker Button
- 30Tailwind Brutalist Concrete Slab Button
- 31Tailwind Mercury Bead Morph Button
- 32Tailwind Soft Pill Modern SaaS Button
- 33Tailwind Masking Tape Label Button
- 34Tailwind Obsidian Glass Shard Button
- 35Tailwind Wine Velvet Ribbon Button
- 36Tailwind LED Segment Display Button
- 37Tailwind Cassette Mixtape Label Button
- 38Tailwind Chalkboard Menu Button
- 39Tailwind Vinyl Record Sleeve Ripple Button
- 40Tailwind Polaroid Snapshot Button
- 41Tailwind Airline Boarding Pass Button
- 42Tailwind Aged Brass Subway Token Button
- 43Tailwind Vintage Rx Pharmacy Label Button
- 44Tailwind Bakelite Radio Dial Toggle Button
- 45Tailwind Regal Postage Stamp Button
- 46Tailwind Victorian Cigar Band Foil Button
- 47Tailwind Vintage Calculator Key Button
- 48Tailwind Toolbox Latch Spotlight Button
- 49Tailwind Receipt Paper Total Count-Up Button
- 50Tailwind IBM Punch Card Magnetic Follow Button
- 51Tailwind Brass Periscope Lens Button
Tailwind Subscribe Confetti Button
Subscribe once and eighteen paper shards fan out on individual trajectories, the bell rings, and the label flips to "Subscribed". Every particle's angle, distance, spin and delay is a custom property in its style attribute — the one place where inline style beats a utility, and the port says so.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tb-14 w-full min-h-svh grid place-items-center font-tb14-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative z-10 cursor-pointer inline-grid place-items-center min-h-14 px-8 rounded-full text-base font-extrabold tracking-[-0.01em] text-tb14-ink bg-[linear-gradient(180deg,var(--color-tb14-accent-lift),var(--color-tb14-accent))] shadow-[inset_0_1px_0_var(--color-tb14-sheen),0_16px_34px_-16px_var(--color-tb14-glow)] transition-[scale,background,box-shadow] duration-[350ms] ease-tb14-spring [-webkit-tap-highlight-color:transparent] hover:scale-[1.03] active:scale-[0.97] has-focus-visible:outline-2 has-focus-visible:outline-offset-4 has-focus-visible:outline-tb14-c1 has-checked:bg-[linear-gradient(180deg,var(--color-tb14-surface-lift),var(--color-tb14-surface))] has-checked:shadow-[inset_0_0_0_1px_var(--color-tb14-edge)] motion-reduce:transition-none motion-reduce:hover:scale-100">
<input type="checkbox" aria-label="Subscribe to the newsletter" class="sr-only">
<span aria-hidden="true" class="absolute inset-0 pointer-events-none motion-reduce:hidden">
<span style="--x:-.95;--y:-.65;--r:180deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.6;--y:-1;--r:-140deg;--d:20ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.25;--y:-1.1;--r:220deg;--d:40ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.1;--y:-1.15;--r:-90deg;--d:10ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.45;--y:-1;--r:160deg;--d:50ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.8;--y:-.75;--r:-200deg;--d:30ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.05;--y:-.4;--r:120deg;--d:60ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.1;--y:-.3;--r:-160deg;--d:70ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.15;--y:.1;--r:200deg;--d:90ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.15;--y:.05;--r:-240deg;--d:80ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.9;--y:.5;--r:140deg;--d:120ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.95;--y:.45;--r:-120deg;--d:110ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.5;--y:.85;--r:260deg;--d:150ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.55;--y:.8;--r:-180deg;--d:140ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:0;--y:1;--r:90deg;--d:170ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.3;--y:-.5;--r:-60deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.3;--y:-.55;--r:60deg;--d:15ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.7;--y:.2;--r:-300deg;--d:100ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
</span>
<span class="grid grid-flow-col items-center gap-2">
<span aria-hidden="true" class="grid place-items-center size-5 [transform-origin:50%_15%] group-has-checked:animate-tb14-ring motion-reduce:group-has-checked:animate-none">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="size-5"><path d="M18 15.5V11a6 6 0 1 0-12 0v4.5L4.5 18h15L18 15.5z"/><path d="M10 20.5a2.2 2.2 0 0 0 4 0"/></svg>
</span>
<span class="grid">
<span class="col-start-1 row-start-1 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-0 group-has-checked:-translate-y-1.5 motion-reduce:transition-none">Subscribe</span>
<span class="col-start-1 row-start-1 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-100 group-has-checked:translate-y-0 motion-reduce:transition-none">Subscribed</span>
</span>
</span>
</label>
</div><div class="tb-14 w-full min-h-svh grid place-items-center font-tb14-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative z-10 cursor-pointer inline-grid place-items-center min-h-14 px-8 rounded-full text-base font-extrabold tracking-[-0.01em] text-tb14-ink bg-[linear-gradient(180deg,var(--color-tb14-accent-lift),var(--color-tb14-accent))] shadow-[inset_0_1px_0_var(--color-tb14-sheen),0_16px_34px_-16px_var(--color-tb14-glow)] transition-[scale,background,box-shadow] duration-[350ms] ease-tb14-spring [-webkit-tap-highlight-color:transparent] hover:scale-[1.03] active:scale-[0.97] has-focus-visible:outline-2 has-focus-visible:outline-offset-4 has-focus-visible:outline-tb14-c1 has-checked:bg-[linear-gradient(180deg,var(--color-tb14-surface-lift),var(--color-tb14-surface))] has-checked:shadow-[inset_0_0_0_1px_var(--color-tb14-edge)] motion-reduce:transition-none motion-reduce:hover:scale-100">
<input type="checkbox" aria-label="Subscribe to the newsletter" class="sr-only">
<span aria-hidden="true" class="absolute inset-0 pointer-events-none motion-reduce:hidden">
<span style="--x:-.95;--y:-.65;--r:180deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.6;--y:-1;--r:-140deg;--d:20ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.25;--y:-1.1;--r:220deg;--d:40ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.1;--y:-1.15;--r:-90deg;--d:10ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.45;--y:-1;--r:160deg;--d:50ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.8;--y:-.75;--r:-200deg;--d:30ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.05;--y:-.4;--r:120deg;--d:60ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.1;--y:-.3;--r:-160deg;--d:70ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.15;--y:.1;--r:200deg;--d:90ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.15;--y:.05;--r:-240deg;--d:80ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.9;--y:.5;--r:140deg;--d:120ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.95;--y:.45;--r:-120deg;--d:110ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.5;--y:.85;--r:260deg;--d:150ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.55;--y:.8;--r:-180deg;--d:140ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:0;--y:1;--r:90deg;--d:170ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.3;--y:-.5;--r:-60deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.3;--y:-.55;--r:60deg;--d:15ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.7;--y:.2;--r:-300deg;--d:100ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
</span>
<span class="grid grid-flow-col items-center gap-2">
<span aria-hidden="true" class="grid place-items-center size-5 [transform-origin:50%_15%] group-has-checked:animate-tb14-ring motion-reduce:group-has-checked:animate-none">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="size-5"><path d="M18 15.5V11a6 6 0 1 0-12 0v4.5L4.5 18h15L18 15.5z"/><path d="M10 20.5a2.2 2.2 0 0 0 4 0"/></svg>
</span>
<span class="grid">
<span class="col-start-1 row-start-1 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-0 group-has-checked:-translate-y-1.5 motion-reduce:transition-none">Subscribe</span>
<span class="col-start-1 row-start-1 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-100 group-has-checked:translate-y-0 motion-reduce:transition-none">Subscribed</span>
</span>
</span>
</label>
</div>@import "tailwindcss";
@theme {
--color-tb14-bg: #100c1a;
--color-tb14-bg: oklch(0.16 0.03 295);
--color-tb14-surface: #1d1730;
--color-tb14-surface: oklch(0.25 0.05 295);
--color-tb14-surface-lift: #272040;
--color-tb14-surface-lift: oklch(0.3 0.05 295);
--color-tb14-ink: #f8f5ff;
--color-tb14-ink: oklch(0.97 0.012 295);
--color-tb14-accent: #f43f5e;
--color-tb14-accent: oklch(0.64 0.21 12);
--color-tb14-accent-lift: #f76b83;
--color-tb14-accent-lift: oklch(0.7 0.18 12);
--color-tb14-c1: #fbbf24;
--color-tb14-c1: oklch(0.84 0.16 82);
--color-tb14-c2: #34d399;
--color-tb14-c2: oklch(0.8 0.14 165);
--color-tb14-c3: #60a5fa;
--color-tb14-c3: oklch(0.76 0.13 253);
--color-tb14-sheen: #ffffff4d;
--color-tb14-edge: #f8f5ff2e;
--color-tb14-edge: oklch(0.97 0.012 295 / 0.18);
--color-tb14-glow: #f43f5ed9;
--color-tb14-glow: oklch(0.64 0.21 12 / 0.85);
--font-tb14-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb14-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-tb14-spring: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb14-burst: tb-14-burst 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d, 0ms) forwards;
--animate-tb14-ring: tb-14-ring 0.8s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@supports (transition-timing-function: linear(0, 1)) {
@theme {
--ease-tb14-spring: linear(0, 0.42 8%, 0.86 17%, 1.12 25%, 1.06 36%, 1 50%, 1);
}
}
@keyframes tb-14-burst {
0% {
opacity: 0;
translate: 0 0;
scale: 0.3;
rotate: 0deg;
}
12% {
opacity: 1;
}
100% {
opacity: 0;
translate: calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate: var(--r);
scale: 1;
}
}
@keyframes tb-14-ring {
0%,
100% {
rotate: 0deg;
}
15% {
rotate: 16deg;
}
32% {
rotate: -13deg;
}
50% {
rotate: 9deg;
}
68% {
rotate: -6deg;
}
85% {
rotate: 3deg;
}
}
.tb-14 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(46rem 28rem at 50% 40%, color-mix(in oklab, var(--color-tb14-accent) 14%, transparent), transparent 70%), var(--color-tb14-bg);
}
@media (prefers-color-scheme: light) {
.tb-14:not([data-theme="dark"]) {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-surface-lift: oklch(0.99 0.004 295);
--color-tb14-ink: oklch(0.18 0.05 295);
--color-tb14-edge: oklch(0.18 0.05 295 / 0.18);
}
}
.tb-14[data-theme="light"] {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-ink: oklch(0.18 0.05 295);
}
@media (forced-colors: active) {
.tb-14 label {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.tb-14 label > span[aria-hidden] span {
display: none;
}
}@import "tailwindcss";
@theme {
--color-tb14-bg: #100c1a;
--color-tb14-bg: oklch(0.16 0.03 295);
--color-tb14-surface: #1d1730;
--color-tb14-surface: oklch(0.25 0.05 295);
--color-tb14-surface-lift: #272040;
--color-tb14-surface-lift: oklch(0.3 0.05 295);
--color-tb14-ink: #f8f5ff;
--color-tb14-ink: oklch(0.97 0.012 295);
--color-tb14-accent: #f43f5e;
--color-tb14-accent: oklch(0.64 0.21 12);
--color-tb14-accent-lift: #f76b83;
--color-tb14-accent-lift: oklch(0.7 0.18 12);
--color-tb14-c1: #fbbf24;
--color-tb14-c1: oklch(0.84 0.16 82);
--color-tb14-c2: #34d399;
--color-tb14-c2: oklch(0.8 0.14 165);
--color-tb14-c3: #60a5fa;
--color-tb14-c3: oklch(0.76 0.13 253);
--color-tb14-sheen: #ffffff4d;
--color-tb14-edge: #f8f5ff2e;
--color-tb14-edge: oklch(0.97 0.012 295 / 0.18);
--color-tb14-glow: #f43f5ed9;
--color-tb14-glow: oklch(0.64 0.21 12 / 0.85);
--font-tb14-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb14-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-tb14-spring: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb14-burst: tb-14-burst 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d, 0ms) forwards;
--animate-tb14-ring: tb-14-ring 0.8s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@supports (transition-timing-function: linear(0, 1)) {
@theme {
--ease-tb14-spring: linear(0, 0.42 8%, 0.86 17%, 1.12 25%, 1.06 36%, 1 50%, 1);
}
}
@keyframes tb-14-burst {
0% {
opacity: 0;
translate: 0 0;
scale: 0.3;
rotate: 0deg;
}
12% {
opacity: 1;
}
100% {
opacity: 0;
translate: calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate: var(--r);
scale: 1;
}
}
@keyframes tb-14-ring {
0%,
100% {
rotate: 0deg;
}
15% {
rotate: 16deg;
}
32% {
rotate: -13deg;
}
50% {
rotate: 9deg;
}
68% {
rotate: -6deg;
}
85% {
rotate: 3deg;
}
}
.tb-14 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(46rem 28rem at 50% 40%, color-mix(in oklab, var(--color-tb14-accent) 14%, transparent), transparent 70%), var(--color-tb14-bg);
}
@media (prefers-color-scheme: light) {
.tb-14:not([data-theme="dark"]) {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-surface-lift: oklch(0.99 0.004 295);
--color-tb14-ink: oklch(0.18 0.05 295);
--color-tb14-edge: oklch(0.18 0.05 295 / 0.18);
}
}
.tb-14[data-theme="light"] {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-ink: oklch(0.18 0.05 295);
}
@media (forced-colors: active) {
.tb-14 label {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.tb-14 label > span[aria-hidden] span {
display: none;
}
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tb14-bg: #100c1a;
--color-tb14-bg: oklch(0.16 0.03 295);
--color-tb14-surface: #1d1730;
--color-tb14-surface: oklch(0.25 0.05 295);
--color-tb14-surface-lift: #272040;
--color-tb14-surface-lift: oklch(0.3 0.05 295);
--color-tb14-ink: #f8f5ff;
--color-tb14-ink: oklch(0.97 0.012 295);
--color-tb14-accent: #f43f5e;
--color-tb14-accent: oklch(0.64 0.21 12);
--color-tb14-accent-lift: #f76b83;
--color-tb14-accent-lift: oklch(0.7 0.18 12);
--color-tb14-c1: #fbbf24;
--color-tb14-c1: oklch(0.84 0.16 82);
--color-tb14-c2: #34d399;
--color-tb14-c2: oklch(0.8 0.14 165);
--color-tb14-c3: #60a5fa;
--color-tb14-c3: oklch(0.76 0.13 253);
--color-tb14-sheen: #ffffff4d;
--color-tb14-edge: #f8f5ff2e;
--color-tb14-edge: oklch(0.97 0.012 295 / 0.18);
--color-tb14-glow: #f43f5ed9;
--color-tb14-glow: oklch(0.64 0.21 12 / 0.85);
--font-tb14-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb14-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-tb14-spring: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb14-burst: tb-14-burst 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d, 0ms) forwards;
--animate-tb14-ring: tb-14-ring 0.8s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@supports (transition-timing-function: linear(0, 1)) {
@theme {
--ease-tb14-spring: linear(0, 0.42 8%, 0.86 17%, 1.12 25%, 1.06 36%, 1 50%, 1);
}
}
@keyframes tb-14-burst {
0% {
opacity: 0;
translate: 0 0;
scale: 0.3;
rotate: 0deg;
}
12% {
opacity: 1;
}
100% {
opacity: 0;
translate: calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate: var(--r);
scale: 1;
}
}
@keyframes tb-14-ring {
0%,
100% {
rotate: 0deg;
}
15% {
rotate: 16deg;
}
32% {
rotate: -13deg;
}
50% {
rotate: 9deg;
}
68% {
rotate: -6deg;
}
85% {
rotate: 3deg;
}
}
.tb-14 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(46rem 28rem at 50% 40%, color-mix(in oklab, var(--color-tb14-accent) 14%, transparent), transparent 70%), var(--color-tb14-bg);
}
@media (prefers-color-scheme: light) {
.tb-14:not([data-theme="dark"]) {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-surface-lift: oklch(0.99 0.004 295);
--color-tb14-ink: oklch(0.18 0.05 295);
--color-tb14-edge: oklch(0.18 0.05 295 / 0.18);
}
}
.tb-14[data-theme="light"] {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-ink: oklch(0.18 0.05 295);
}
@media (forced-colors: active) {
.tb-14 label {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.tb-14 label > span[aria-hidden] span {
display: none;
}
}@import "tailwindcss";
@theme {
--color-tb14-bg: #100c1a;
--color-tb14-bg: oklch(0.16 0.03 295);
--color-tb14-surface: #1d1730;
--color-tb14-surface: oklch(0.25 0.05 295);
--color-tb14-surface-lift: #272040;
--color-tb14-surface-lift: oklch(0.3 0.05 295);
--color-tb14-ink: #f8f5ff;
--color-tb14-ink: oklch(0.97 0.012 295);
--color-tb14-accent: #f43f5e;
--color-tb14-accent: oklch(0.64 0.21 12);
--color-tb14-accent-lift: #f76b83;
--color-tb14-accent-lift: oklch(0.7 0.18 12);
--color-tb14-c1: #fbbf24;
--color-tb14-c1: oklch(0.84 0.16 82);
--color-tb14-c2: #34d399;
--color-tb14-c2: oklch(0.8 0.14 165);
--color-tb14-c3: #60a5fa;
--color-tb14-c3: oklch(0.76 0.13 253);
--color-tb14-sheen: #ffffff4d;
--color-tb14-edge: #f8f5ff2e;
--color-tb14-edge: oklch(0.97 0.012 295 / 0.18);
--color-tb14-glow: #f43f5ed9;
--color-tb14-glow: oklch(0.64 0.21 12 / 0.85);
--font-tb14-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb14-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-tb14-spring: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb14-burst: tb-14-burst 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d, 0ms) forwards;
--animate-tb14-ring: tb-14-ring 0.8s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@supports (transition-timing-function: linear(0, 1)) {
@theme {
--ease-tb14-spring: linear(0, 0.42 8%, 0.86 17%, 1.12 25%, 1.06 36%, 1 50%, 1);
}
}
@keyframes tb-14-burst {
0% {
opacity: 0;
translate: 0 0;
scale: 0.3;
rotate: 0deg;
}
12% {
opacity: 1;
}
100% {
opacity: 0;
translate: calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate: var(--r);
scale: 1;
}
}
@keyframes tb-14-ring {
0%,
100% {
rotate: 0deg;
}
15% {
rotate: 16deg;
}
32% {
rotate: -13deg;
}
50% {
rotate: 9deg;
}
68% {
rotate: -6deg;
}
85% {
rotate: 3deg;
}
}
.tb-14 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(46rem 28rem at 50% 40%, color-mix(in oklab, var(--color-tb14-accent) 14%, transparent), transparent 70%), var(--color-tb14-bg);
}
@media (prefers-color-scheme: light) {
.tb-14:not([data-theme="dark"]) {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-surface-lift: oklch(0.99 0.004 295);
--color-tb14-ink: oklch(0.18 0.05 295);
--color-tb14-edge: oklch(0.18 0.05 295 / 0.18);
}
}
.tb-14[data-theme="light"] {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-ink: oklch(0.18 0.05 295);
}
@media (forced-colors: active) {
.tb-14 label {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.tb-14 label > span[aria-hidden] span {
display: none;
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700;800&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Utilities: the button skin, both label states, the bell pivot and ring, each shard's box and colour, and the burst trigger. CSS: two keyframe blocks, the stage backdrop and the
linear()spring. - Inline
styleis the right answer for per-particle data, and this is the demo that proves it. Eighteen shards × four values would be seventy-two single-use arbitrary utilities; the CSS version needed eighteennth-childrules. A custom property in the style attribute is shorter than both and reads as data, which is what it is. - The
nth-child(3n)shape/colour rotation became explicit per-shard classes. Slightly longer, and now you can see that shard seven is a blue rectangle without counting. --animate-tb14-burstembedsvar(--d, 0ms)as its delay, so the per-shard stagger rides the same token. That fallback matters: a shard without--dstill animates.
Here's a working CSS demo 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: Tailwind Subscribe Confetti Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-subscribe-confetti-button/
Subscribe once and eighteen paper shards fan out on individual trajectories, the bell rings, and the label flips to "Subscribed". Every particle's angle, distance, spin and delay is a custom property in its style attribute — the one place where inline style beats a utility, and the port says so.
## HTML
```html
<div class="tb-14 w-full min-h-svh grid place-items-center font-tb14-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative z-10 cursor-pointer inline-grid place-items-center min-h-14 px-8 rounded-full text-base font-extrabold tracking-[-0.01em] text-tb14-ink bg-[linear-gradient(180deg,var(--color-tb14-accent-lift),var(--color-tb14-accent))] shadow-[inset_0_1px_0_var(--color-tb14-sheen),0_16px_34px_-16px_var(--color-tb14-glow)] transition-[scale,background,box-shadow] duration-[350ms] ease-tb14-spring [-webkit-tap-highlight-color:transparent] hover:scale-[1.03] active:scale-[0.97] has-focus-visible:outline-2 has-focus-visible:outline-offset-4 has-focus-visible:outline-tb14-c1 has-checked:bg-[linear-gradient(180deg,var(--color-tb14-surface-lift),var(--color-tb14-surface))] has-checked:shadow-[inset_0_0_0_1px_var(--color-tb14-edge)] motion-reduce:transition-none motion-reduce:hover:scale-100">
<input type="checkbox" aria-label="Subscribe to the newsletter" class="sr-only">
<span aria-hidden="true" class="absolute inset-0 pointer-events-none motion-reduce:hidden">
<span style="--x:-.95;--y:-.65;--r:180deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.6;--y:-1;--r:-140deg;--d:20ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.25;--y:-1.1;--r:220deg;--d:40ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.1;--y:-1.15;--r:-90deg;--d:10ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.45;--y:-1;--r:160deg;--d:50ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.8;--y:-.75;--r:-200deg;--d:30ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.05;--y:-.4;--r:120deg;--d:60ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.1;--y:-.3;--r:-160deg;--d:70ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.15;--y:.1;--r:200deg;--d:90ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.15;--y:.05;--r:-240deg;--d:80ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.9;--y:.5;--r:140deg;--d:120ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.95;--y:.45;--r:-120deg;--d:110ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.5;--y:.85;--r:260deg;--d:150ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.55;--y:.8;--r:-180deg;--d:140ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:0;--y:1;--r:90deg;--d:170ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.3;--y:-.5;--r:-60deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.3;--y:-.55;--r:60deg;--d:15ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.7;--y:.2;--r:-300deg;--d:100ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
</span>
<span class="grid grid-flow-col items-center gap-2">
<span aria-hidden="true" class="grid place-items-center size-5 [transform-origin:50%_15%] group-has-checked:animate-tb14-ring motion-reduce:group-has-checked:animate-none">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="size-5"><path d="M18 15.5V11a6 6 0 1 0-12 0v4.5L4.5 18h15L18 15.5z"/><path d="M10 20.5a2.2 2.2 0 0 0 4 0"/></svg>
</span>
<span class="grid">
<span class="col-start-1 row-start-1 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-0 group-has-checked:-translate-y-1.5 motion-reduce:transition-none">Subscribe</span>
<span class="col-start-1 row-start-1 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-100 group-has-checked:translate-y-0 motion-reduce:transition-none">Subscribed</span>
</span>
</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb14-bg: #100c1a;
--color-tb14-bg: oklch(0.16 0.03 295);
--color-tb14-surface: #1d1730;
--color-tb14-surface: oklch(0.25 0.05 295);
--color-tb14-surface-lift: #272040;
--color-tb14-surface-lift: oklch(0.3 0.05 295);
--color-tb14-ink: #f8f5ff;
--color-tb14-ink: oklch(0.97 0.012 295);
--color-tb14-accent: #f43f5e;
--color-tb14-accent: oklch(0.64 0.21 12);
--color-tb14-accent-lift: #f76b83;
--color-tb14-accent-lift: oklch(0.7 0.18 12);
--color-tb14-c1: #fbbf24;
--color-tb14-c1: oklch(0.84 0.16 82);
--color-tb14-c2: #34d399;
--color-tb14-c2: oklch(0.8 0.14 165);
--color-tb14-c3: #60a5fa;
--color-tb14-c3: oklch(0.76 0.13 253);
--color-tb14-sheen: #ffffff4d;
--color-tb14-edge: #f8f5ff2e;
--color-tb14-edge: oklch(0.97 0.012 295 / 0.18);
--color-tb14-glow: #f43f5ed9;
--color-tb14-glow: oklch(0.64 0.21 12 / 0.85);
--font-tb14-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb14-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-tb14-spring: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb14-burst: tb-14-burst 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d, 0ms) forwards;
--animate-tb14-ring: tb-14-ring 0.8s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@supports (transition-timing-function: linear(0, 1)) {
@theme {
--ease-tb14-spring: linear(0, 0.42 8%, 0.86 17%, 1.12 25%, 1.06 36%, 1 50%, 1);
}
}
@keyframes tb-14-burst {
0% {
opacity: 0;
translate: 0 0;
scale: 0.3;
rotate: 0deg;
}
12% {
opacity: 1;
}
100% {
opacity: 0;
translate: calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate: var(--r);
scale: 1;
}
}
@keyframes tb-14-ring {
0%,
100% {
rotate: 0deg;
}
15% {
rotate: 16deg;
}
32% {
rotate: -13deg;
}
50% {
rotate: 9deg;
}
68% {
rotate: -6deg;
}
85% {
rotate: 3deg;
}
}
.tb-14 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(46rem 28rem at 50% 40%, color-mix(in oklab, var(--color-tb14-accent) 14%, transparent), transparent 70%), var(--color-tb14-bg);
}
@media (prefers-color-scheme: light) {
.tb-14:not([data-theme="dark"]) {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-surface-lift: oklch(0.99 0.004 295);
--color-tb14-ink: oklch(0.18 0.05 295);
--color-tb14-edge: oklch(0.18 0.05 295 / 0.18);
}
}
.tb-14[data-theme="light"] {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-ink: oklch(0.18 0.05 295);
}
@media (forced-colors: active) {
.tb-14 label {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.tb-14 label > span[aria-hidden] span {
display: none;
}
}
```Here's a working CSS demo 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: Tailwind Subscribe Confetti Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-subscribe-confetti-button/
Subscribe once and eighteen paper shards fan out on individual trajectories, the bell rings, and the label flips to "Subscribed". Every particle's angle, distance, spin and delay is a custom property in its style attribute — the one place where inline style beats a utility, and the port says so.
## HTML
```html
<div class="tb-14 w-full min-h-svh grid place-items-center font-tb14-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative z-10 cursor-pointer inline-grid place-items-center min-h-14 px-8 rounded-full text-base font-extrabold tracking-[-0.01em] text-tb14-ink bg-[linear-gradient(180deg,var(--color-tb14-accent-lift),var(--color-tb14-accent))] shadow-[inset_0_1px_0_var(--color-tb14-sheen),0_16px_34px_-16px_var(--color-tb14-glow)] transition-[scale,background,box-shadow] duration-[350ms] ease-tb14-spring [-webkit-tap-highlight-color:transparent] hover:scale-[1.03] active:scale-[0.97] has-focus-visible:outline-2 has-focus-visible:outline-offset-4 has-focus-visible:outline-tb14-c1 has-checked:bg-[linear-gradient(180deg,var(--color-tb14-surface-lift),var(--color-tb14-surface))] has-checked:shadow-[inset_0_0_0_1px_var(--color-tb14-edge)] motion-reduce:transition-none motion-reduce:hover:scale-100">
<input type="checkbox" aria-label="Subscribe to the newsletter" class="sr-only">
<span aria-hidden="true" class="absolute inset-0 pointer-events-none motion-reduce:hidden">
<span style="--x:-.95;--y:-.65;--r:180deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.6;--y:-1;--r:-140deg;--d:20ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.25;--y:-1.1;--r:220deg;--d:40ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.1;--y:-1.15;--r:-90deg;--d:10ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.45;--y:-1;--r:160deg;--d:50ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.8;--y:-.75;--r:-200deg;--d:30ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.05;--y:-.4;--r:120deg;--d:60ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.1;--y:-.3;--r:-160deg;--d:70ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-1.15;--y:.1;--r:200deg;--d:90ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:1.15;--y:.05;--r:-240deg;--d:80ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.9;--y:.5;--r:140deg;--d:120ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.95;--y:.45;--r:-120deg;--d:110ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.5;--y:.85;--r:260deg;--d:150ms" class="absolute top-1/2 left-1/2 w-[0.28rem] h-[0.6rem] -mt-[0.3rem] -ml-[0.14rem] rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.55;--y:.8;--r:-180deg;--d:140ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:0;--y:1;--r:90deg;--d:170ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:-.3;--y:-.5;--r:-60deg;--d:0ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c1 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.3;--y:-.55;--r:60deg;--d:15ms" class="absolute top-1/2 left-1/2 w-2 h-[0.32rem] -mt-[0.16rem] -ml-1 rounded-[0.06rem] bg-tb14-c3 opacity-0 group-has-checked:animate-tb14-burst"></span>
<span style="--x:.7;--y:.2;--r:-300deg;--d:100ms" class="absolute top-1/2 left-1/2 size-[0.34rem] -mt-[0.17rem] -ml-[0.17rem] rounded-full bg-tb14-c2 opacity-0 group-has-checked:animate-tb14-burst"></span>
</span>
<span class="grid grid-flow-col items-center gap-2">
<span aria-hidden="true" class="grid place-items-center size-5 [transform-origin:50%_15%] group-has-checked:animate-tb14-ring motion-reduce:group-has-checked:animate-none">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="size-5"><path d="M18 15.5V11a6 6 0 1 0-12 0v4.5L4.5 18h15L18 15.5z"/><path d="M10 20.5a2.2 2.2 0 0 0 4 0"/></svg>
</span>
<span class="grid">
<span class="col-start-1 row-start-1 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-0 group-has-checked:-translate-y-1.5 motion-reduce:transition-none">Subscribe</span>
<span class="col-start-1 row-start-1 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tb14-out group-has-checked:opacity-100 group-has-checked:translate-y-0 motion-reduce:transition-none">Subscribed</span>
</span>
</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb14-bg: #100c1a;
--color-tb14-bg: oklch(0.16 0.03 295);
--color-tb14-surface: #1d1730;
--color-tb14-surface: oklch(0.25 0.05 295);
--color-tb14-surface-lift: #272040;
--color-tb14-surface-lift: oklch(0.3 0.05 295);
--color-tb14-ink: #f8f5ff;
--color-tb14-ink: oklch(0.97 0.012 295);
--color-tb14-accent: #f43f5e;
--color-tb14-accent: oklch(0.64 0.21 12);
--color-tb14-accent-lift: #f76b83;
--color-tb14-accent-lift: oklch(0.7 0.18 12);
--color-tb14-c1: #fbbf24;
--color-tb14-c1: oklch(0.84 0.16 82);
--color-tb14-c2: #34d399;
--color-tb14-c2: oklch(0.8 0.14 165);
--color-tb14-c3: #60a5fa;
--color-tb14-c3: oklch(0.76 0.13 253);
--color-tb14-sheen: #ffffff4d;
--color-tb14-edge: #f8f5ff2e;
--color-tb14-edge: oklch(0.97 0.012 295 / 0.18);
--color-tb14-glow: #f43f5ed9;
--color-tb14-glow: oklch(0.64 0.21 12 / 0.85);
--font-tb14-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb14-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-tb14-spring: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb14-burst: tb-14-burst 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d, 0ms) forwards;
--animate-tb14-ring: tb-14-ring 0.8s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@supports (transition-timing-function: linear(0, 1)) {
@theme {
--ease-tb14-spring: linear(0, 0.42 8%, 0.86 17%, 1.12 25%, 1.06 36%, 1 50%, 1);
}
}
@keyframes tb-14-burst {
0% {
opacity: 0;
translate: 0 0;
scale: 0.3;
rotate: 0deg;
}
12% {
opacity: 1;
}
100% {
opacity: 0;
translate: calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate: var(--r);
scale: 1;
}
}
@keyframes tb-14-ring {
0%,
100% {
rotate: 0deg;
}
15% {
rotate: 16deg;
}
32% {
rotate: -13deg;
}
50% {
rotate: 9deg;
}
68% {
rotate: -6deg;
}
85% {
rotate: 3deg;
}
}
.tb-14 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(46rem 28rem at 50% 40%, color-mix(in oklab, var(--color-tb14-accent) 14%, transparent), transparent 70%), var(--color-tb14-bg);
}
@media (prefers-color-scheme: light) {
.tb-14:not([data-theme="dark"]) {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-surface-lift: oklch(0.99 0.004 295);
--color-tb14-ink: oklch(0.18 0.05 295);
--color-tb14-edge: oklch(0.18 0.05 295 / 0.18);
}
}
.tb-14[data-theme="light"] {
--color-tb14-bg: oklch(0.96 0.012 295);
--color-tb14-surface: #ffffff;
--color-tb14-ink: oklch(0.18 0.05 295);
}
@media (forced-colors: active) {
.tb-14 label {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.tb-14 label > span[aria-hidden] span {
display: none;
}
}
```How this works
Per-particle data stays inline. Eighteen shards each need four unique numbers. As utilities that would be seventy-two arbitrary values; as CSS it was eighteen nth-child rules. Inline custom properties are simply the correct tool — and Tailwind reads them fine from the keyframe:
@keyframes tb-14-burst{
0% { opacity:0; translate:0 0; scale:.3; }
12% { opacity:1; }
100%{ opacity:0; translate:calc(var(--x) * 5.5rem) calc(var(--y) * 5.5rem);
rotate:var(--r); scale:1; }
}Variety is what separates confetti from a firework. Half the shards are rectangles, a third are circles, and three colour tokens alternate — expressed here as three shape class lists instead of nth-child(3n) selectors, which makes each shard's look visible on the shard.
The celebration is never the message. The label flip and the checked state carry the outcome; under motion-reduce: the shards are hidden and the button still says "Subscribed".
Make it yours
- Burst radius is the
5.5remmultiplier in the keyframe — one number for the whole spread. - Retint through
--color-tb14-c1/c2/c3; the shards already alternate. - More shards: copy a span with new
--x/--y/--r/--d. No CSS changes. - Fire from JS instead of
:checkedby toggling a class and usinggroup-[.is-on]:animate-tb14-burst. - Quieter product: delete the bell shake and keep the label flip — the celebration still lands.
Gotchas — read before shipping
- A
:checked-driven burst plays once. Repeat bursts from a button need a class removed and re-added with a forced reflow. - 18 animated absolutely-positioned elements is fine; 200 is not. Stay under ~30 at button scale and never animate their layout properties.
pointer-events-noneon the particle layer is mandatory, or shards eat clicks mid-flight.- Particles inside an
overflow-hiddenancestor get clipped. This wrapper is deliberately unclipped. - Don't reach for arbitrary utilities to express per-particle data. Seventy-two one-off values would compile seventy-two rules for no gain.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Individual transform properties and :has() are both well below Tailwind v4's floor. Un-registered custom properties inside calc() work here because the animated property (translate) is itself typed.