24 / 36
All 36 in 36 Tailwind CSS Button Hover Effects
- 01Tailwind Magnetic Ripple Attraction Button Hover Effect
- 02Tailwind Cyberpunk Holographic Glitch Grid Button
- 03Tailwind Frosted Glassmorphism Shimmer Glow Button
- 04Tailwind Organic Blob Morph Button Hover Effect
- 05Tailwind Dual-Line Intersection Border Draw Button
- 06Tailwind Isometric Tactile 3D Depress Button
- 07Tailwind Infinite Sliding Kinetic Marquee Button
- 08Tailwind Chroma Conic Gradient Border Spin Button
- 09Tailwind Minimalist Border & Outline Drawing Hover Effects
- 10Tailwind Modern 3D Skeuomorphic Button Press Effects
- 11Tailwind Neo-Brutalist & Cyberpunk Button Glow Effects
- 12Tailwind Sliding Background Color Wipe Button Effects
- 13Tailwind Liquid Fill & Organic Morphing Button Effects
- 14Tailwind Kinetic Text Flip & Icon Reveal Button Effects
- 15Tailwind Liquid Wave Fill Button Hover Effect
- 16Tailwind Magnetic Border Drift Button
- 17Tailwind Glitch Slice Button Hover Effect
- 18Tailwind Neon Pulse Glow Button Hover Effect
- 19Tailwind Split Reveal Curtain Button Effect
- 20Tailwind Diagonal Shutter Sweep Button Effect
- 21Tailwind Elastic Lift Spring Button Hover Effect
- 22Tailwind Center Underline Draw Link Button Effects
- 23Tailwind 3D Flip Card Button Hover Effect
- 24Tailwind Particle Burst Confetti Button Effect
- 25Tailwind Slide Doors Closing Panel Button Effect
- 26Tailwind Typewriter Retype Terminal Button Effect
- 27Tailwind Hard Shadow Shift Flat Button Effect
- 28Tailwind Material Ripple Wave Button Effect
- 29Tailwind Rainbow Gradient Border Spin Button
- 30Tailwind Morph Shape Silhouette Button Effects
- 31Tailwind Icon Slide In Directional Button Effect
- 32Tailwind Cursor Spotlight Glow Button
- 33Tailwind Stagger Fill Equaliser Button Effect
- 34Tailwind Sunburst Expand Radiant Button Effect
- 35Tailwind 3D Press Plunger Key Button Effect
- 36Tailwind Add-to-Cart Liquid Glass Ripple Button
Tailwind Particle Burst Confetti Button Effect
A celebratory CTA that scatters particles outward on hover — confetti-style points fly out and fade. Every particle is a box-shadow clone of one 6px dot, which means eight particles cost one element and zero extra DOM.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-24-group w-full min-h-svh flex flex-wrap">
<section class="tbh-24a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24a-stage" aria-label="Particle burst button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-white bg-tbh24a-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p4 opacity-0 pointer-events-none shadow-[34px_-18px_var(--color-tbh24a-p1),-40px_-10px_var(--color-tbh24a-p2),54px_12px_var(--color-tbh24a-p3),-30px_20px_var(--color-tbh24a-p4)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p2 opacity-0 pointer-events-none shadow-[20px_-30px_var(--color-tbh24a-p2),-52px_6px_var(--color-tbh24a-p4),44px_26px_var(--color-tbh24a-p1),-18px_-26px_var(--color-tbh24a-p3)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Like</span>
</button>
</section>
<section class="tbh-24b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24b-stage" aria-label="Spark ring button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-tbh24b-ink bg-tbh24b-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -ml-1 -mt-1 w-2 h-2 rounded-full bg-transparent opacity-0 scale-[.3] pointer-events-none shadow-[0_-34px_var(--color-tbh24b-acc),24px_-24px_var(--color-tbh24b-acc),34px_0_var(--color-tbh24b-acc),24px_24px_var(--color-tbh24b-acc),0_34px_var(--color-tbh24b-acc),-24px_24px_var(--color-tbh24b-acc),-34px_0_var(--color-tbh24b-acc),-24px_-24px_var(--color-tbh24b-acc)] group-hover/btn:animate-tbh24b-spark group-focus-visible/btn:animate-tbh24b-spark motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Boost</span>
</button>
</section>
</div><div class="tbh-24-group w-full min-h-svh flex flex-wrap">
<section class="tbh-24a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24a-stage" aria-label="Particle burst button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-white bg-tbh24a-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p4 opacity-0 pointer-events-none shadow-[34px_-18px_var(--color-tbh24a-p1),-40px_-10px_var(--color-tbh24a-p2),54px_12px_var(--color-tbh24a-p3),-30px_20px_var(--color-tbh24a-p4)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p2 opacity-0 pointer-events-none shadow-[20px_-30px_var(--color-tbh24a-p2),-52px_6px_var(--color-tbh24a-p4),44px_26px_var(--color-tbh24a-p1),-18px_-26px_var(--color-tbh24a-p3)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Like</span>
</button>
</section>
<section class="tbh-24b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24b-stage" aria-label="Spark ring button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-tbh24b-ink bg-tbh24b-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -ml-1 -mt-1 w-2 h-2 rounded-full bg-transparent opacity-0 scale-[.3] pointer-events-none shadow-[0_-34px_var(--color-tbh24b-acc),24px_-24px_var(--color-tbh24b-acc),34px_0_var(--color-tbh24b-acc),24px_24px_var(--color-tbh24b-acc),0_34px_var(--color-tbh24b-acc),-24px_24px_var(--color-tbh24b-acc),-34px_0_var(--color-tbh24b-acc),-24px_-24px_var(--color-tbh24b-acc)] group-hover/btn:animate-tbh24b-spark group-focus-visible/btn:animate-tbh24b-spark motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Boost</span>
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh24a-acc: #e0492f;
--color-tbh24a-acc: oklch(0.62 0.22 20);
--color-tbh24a-p1: #ff7ab6;
--color-tbh24a-p1: oklch(0.78 0.16 350);
--color-tbh24a-p2: #7ce0ff;
--color-tbh24a-p2: oklch(0.87 0.11 215);
--color-tbh24a-p3: #b6ff7a;
--color-tbh24a-p3: oklch(0.92 0.18 130);
--color-tbh24a-p4: #ffd15c;
--color-tbh24a-p4: oklch(0.87 0.15 90);
--color-tbh24a-stage: #120a0e;
--color-tbh24a-stage: oklch(0.15 0.02 20);
--color-tbh24b-acc: #e0b04a;
--color-tbh24b-acc: oklch(0.78 0.15 90);
--color-tbh24b-ink: #1a1206;
--color-tbh24b-ink: oklch(0.22 0.04 85);
--color-tbh24b-stage: #14100a;
--color-tbh24b-stage: oklch(0.16 0.02 85);
--font-tbh24-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh24a-burst: tbh24a-burst 0.6s ease-out;
--animate-tbh24b-spark: tbh24b-spark 0.55s ease-out;
}
/* Stays CSS: two one-shot keyframes. Both hold the -50% centring in translate and animate
only scale and opacity, so the utilities and the keyframe never fight over a property. */
@keyframes tbh24a-burst {
0% {
opacity: 1;
translate: -50% -50%;
scale: 0.2;
}
100% {
opacity: 0;
translate: -50% -50%;
scale: 1.6;
}
}
@keyframes tbh24b-spark {
0% {
opacity: 1;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.5;
}
}
.tbh-24-group {
width: 100%;
min-height: 100svh;
}
.tbh-24a,
.tbh-24b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh24a-acc: #e0492f;
--color-tbh24a-acc: oklch(0.62 0.22 20);
--color-tbh24a-p1: #ff7ab6;
--color-tbh24a-p1: oklch(0.78 0.16 350);
--color-tbh24a-p2: #7ce0ff;
--color-tbh24a-p2: oklch(0.87 0.11 215);
--color-tbh24a-p3: #b6ff7a;
--color-tbh24a-p3: oklch(0.92 0.18 130);
--color-tbh24a-p4: #ffd15c;
--color-tbh24a-p4: oklch(0.87 0.15 90);
--color-tbh24a-stage: #120a0e;
--color-tbh24a-stage: oklch(0.15 0.02 20);
--color-tbh24b-acc: #e0b04a;
--color-tbh24b-acc: oklch(0.78 0.15 90);
--color-tbh24b-ink: #1a1206;
--color-tbh24b-ink: oklch(0.22 0.04 85);
--color-tbh24b-stage: #14100a;
--color-tbh24b-stage: oklch(0.16 0.02 85);
--font-tbh24-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh24a-burst: tbh24a-burst 0.6s ease-out;
--animate-tbh24b-spark: tbh24b-spark 0.55s ease-out;
}
/* Stays CSS: two one-shot keyframes. Both hold the -50% centring in translate and animate
only scale and opacity, so the utilities and the keyframe never fight over a property. */
@keyframes tbh24a-burst {
0% {
opacity: 1;
translate: -50% -50%;
scale: 0.2;
}
100% {
opacity: 0;
translate: -50% -50%;
scale: 1.6;
}
}
@keyframes tbh24b-spark {
0% {
opacity: 1;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.5;
}
}
.tbh-24-group {
width: 100%;
min-height: 100svh;
}
.tbh-24a,
.tbh-24b {
min-height: 100svh;
}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-tbh24a-acc: #e0492f;
--color-tbh24a-acc: oklch(0.62 0.22 20);
--color-tbh24a-p1: #ff7ab6;
--color-tbh24a-p1: oklch(0.78 0.16 350);
--color-tbh24a-p2: #7ce0ff;
--color-tbh24a-p2: oklch(0.87 0.11 215);
--color-tbh24a-p3: #b6ff7a;
--color-tbh24a-p3: oklch(0.92 0.18 130);
--color-tbh24a-p4: #ffd15c;
--color-tbh24a-p4: oklch(0.87 0.15 90);
--color-tbh24a-stage: #120a0e;
--color-tbh24a-stage: oklch(0.15 0.02 20);
--color-tbh24b-acc: #e0b04a;
--color-tbh24b-acc: oklch(0.78 0.15 90);
--color-tbh24b-ink: #1a1206;
--color-tbh24b-ink: oklch(0.22 0.04 85);
--color-tbh24b-stage: #14100a;
--color-tbh24b-stage: oklch(0.16 0.02 85);
--font-tbh24-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh24a-burst: tbh24a-burst 0.6s ease-out;
--animate-tbh24b-spark: tbh24b-spark 0.55s ease-out;
}
/* Stays CSS: two one-shot keyframes. Both hold the -50% centring in translate and animate
only scale and opacity, so the utilities and the keyframe never fight over a property. */
@keyframes tbh24a-burst {
0% {
opacity: 1;
translate: -50% -50%;
scale: 0.2;
}
100% {
opacity: 0;
translate: -50% -50%;
scale: 1.6;
}
}
@keyframes tbh24b-spark {
0% {
opacity: 1;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.5;
}
}
.tbh-24-group {
width: 100%;
min-height: 100svh;
}
.tbh-24a,
.tbh-24b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh24a-acc: #e0492f;
--color-tbh24a-acc: oklch(0.62 0.22 20);
--color-tbh24a-p1: #ff7ab6;
--color-tbh24a-p1: oklch(0.78 0.16 350);
--color-tbh24a-p2: #7ce0ff;
--color-tbh24a-p2: oklch(0.87 0.11 215);
--color-tbh24a-p3: #b6ff7a;
--color-tbh24a-p3: oklch(0.92 0.18 130);
--color-tbh24a-p4: #ffd15c;
--color-tbh24a-p4: oklch(0.87 0.15 90);
--color-tbh24a-stage: #120a0e;
--color-tbh24a-stage: oklch(0.15 0.02 20);
--color-tbh24b-acc: #e0b04a;
--color-tbh24b-acc: oklch(0.78 0.15 90);
--color-tbh24b-ink: #1a1206;
--color-tbh24b-ink: oklch(0.22 0.04 85);
--color-tbh24b-stage: #14100a;
--color-tbh24b-stage: oklch(0.16 0.02 85);
--font-tbh24-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh24a-burst: tbh24a-burst 0.6s ease-out;
--animate-tbh24b-spark: tbh24b-spark 0.55s ease-out;
}
/* Stays CSS: two one-shot keyframes. Both hold the -50% centring in translate and animate
only scale and opacity, so the utilities and the keyframe never fight over a property. */
@keyframes tbh24a-burst {
0% {
opacity: 1;
translate: -50% -50%;
scale: 0.2;
}
100% {
opacity: 0;
translate: -50% -50%;
scale: 1.6;
}
}
@keyframes tbh24b-spark {
0% {
opacity: 1;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.5;
}
}
.tbh-24-group {
width: 100%;
min-height: 100svh;
}
.tbh-24a,
.tbh-24b {
min-height: 100svh;
}<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=Space+Grotesk:wght@400;500;600;700&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=Space+Grotesk:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
box-shadow-as-particles technique ports verbatim into one long arbitraryshadow-[...]value, and promoting the four particle colours to@themetokens means the palette is editable in one place instead of being eight inline hexes. - Both keyframes were rewritten to hold the centring in
translateand animate onlyscale/opacity— in the CSS original the keyframe restatedtranslate(-50%,-50%)inside a compound transform on every stop. - UI change from the CSS original: the burst now also fires on
group-focus-visible/btn:, so keyboard users get the celebration too — and both stages darkened slightly so the pastel particles read at full contrast.
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 Particle Burst Confetti Button Effect
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-particle-burst-confetti-button-effect/
A celebratory CTA that scatters particles outward on hover — confetti-style points fly out and fade. Every particle is a box-shadow clone of one 6px dot, which means eight particles cost one element and zero extra DOM.
## HTML
```html
<div class="tbh-24-group w-full min-h-svh flex flex-wrap">
<section class="tbh-24a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24a-stage" aria-label="Particle burst button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-white bg-tbh24a-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p4 opacity-0 pointer-events-none shadow-[34px_-18px_var(--color-tbh24a-p1),-40px_-10px_var(--color-tbh24a-p2),54px_12px_var(--color-tbh24a-p3),-30px_20px_var(--color-tbh24a-p4)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p2 opacity-0 pointer-events-none shadow-[20px_-30px_var(--color-tbh24a-p2),-52px_6px_var(--color-tbh24a-p4),44px_26px_var(--color-tbh24a-p1),-18px_-26px_var(--color-tbh24a-p3)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Like</span>
</button>
</section>
<section class="tbh-24b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24b-stage" aria-label="Spark ring button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-tbh24b-ink bg-tbh24b-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -ml-1 -mt-1 w-2 h-2 rounded-full bg-transparent opacity-0 scale-[.3] pointer-events-none shadow-[0_-34px_var(--color-tbh24b-acc),24px_-24px_var(--color-tbh24b-acc),34px_0_var(--color-tbh24b-acc),24px_24px_var(--color-tbh24b-acc),0_34px_var(--color-tbh24b-acc),-24px_24px_var(--color-tbh24b-acc),-34px_0_var(--color-tbh24b-acc),-24px_-24px_var(--color-tbh24b-acc)] group-hover/btn:animate-tbh24b-spark group-focus-visible/btn:animate-tbh24b-spark motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Boost</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh24a-acc: #e0492f;
--color-tbh24a-acc: oklch(0.62 0.22 20);
--color-tbh24a-p1: #ff7ab6;
--color-tbh24a-p1: oklch(0.78 0.16 350);
--color-tbh24a-p2: #7ce0ff;
--color-tbh24a-p2: oklch(0.87 0.11 215);
--color-tbh24a-p3: #b6ff7a;
--color-tbh24a-p3: oklch(0.92 0.18 130);
--color-tbh24a-p4: #ffd15c;
--color-tbh24a-p4: oklch(0.87 0.15 90);
--color-tbh24a-stage: #120a0e;
--color-tbh24a-stage: oklch(0.15 0.02 20);
--color-tbh24b-acc: #e0b04a;
--color-tbh24b-acc: oklch(0.78 0.15 90);
--color-tbh24b-ink: #1a1206;
--color-tbh24b-ink: oklch(0.22 0.04 85);
--color-tbh24b-stage: #14100a;
--color-tbh24b-stage: oklch(0.16 0.02 85);
--font-tbh24-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh24a-burst: tbh24a-burst 0.6s ease-out;
--animate-tbh24b-spark: tbh24b-spark 0.55s ease-out;
}
/* Stays CSS: two one-shot keyframes. Both hold the -50% centring in translate and animate
only scale and opacity, so the utilities and the keyframe never fight over a property. */
@keyframes tbh24a-burst {
0% {
opacity: 1;
translate: -50% -50%;
scale: 0.2;
}
100% {
opacity: 0;
translate: -50% -50%;
scale: 1.6;
}
}
@keyframes tbh24b-spark {
0% {
opacity: 1;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.5;
}
}
.tbh-24-group {
width: 100%;
min-height: 100svh;
}
.tbh-24a,
.tbh-24b {
min-height: 100svh;
}
```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 Particle Burst Confetti Button Effect
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-particle-burst-confetti-button-effect/
A celebratory CTA that scatters particles outward on hover — confetti-style points fly out and fade. Every particle is a box-shadow clone of one 6px dot, which means eight particles cost one element and zero extra DOM.
## HTML
```html
<div class="tbh-24-group w-full min-h-svh flex flex-wrap">
<section class="tbh-24a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24a-stage" aria-label="Particle burst button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-white bg-tbh24a-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p4 opacity-0 pointer-events-none shadow-[34px_-18px_var(--color-tbh24a-p1),-40px_-10px_var(--color-tbh24a-p2),54px_12px_var(--color-tbh24a-p3),-30px_20px_var(--color-tbh24a-p4)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span aria-hidden="true" class="absolute left-1/2 top-1/2 w-1.5 h-1.5 rounded-full bg-tbh24a-p2 opacity-0 pointer-events-none shadow-[20px_-30px_var(--color-tbh24a-p2),-52px_6px_var(--color-tbh24a-p4),44px_26px_var(--color-tbh24a-p1),-18px_-26px_var(--color-tbh24a-p3)] group-hover/btn:animate-tbh24a-burst group-focus-visible/btn:animate-tbh24a-burst motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Like</span>
</button>
</section>
<section class="tbh-24b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh24-sans bg-tbh24b-stage" aria-label="Spark ring button">
<button type="button" class="group/btn relative overflow-visible cursor-pointer border-0 rounded-full px-[42px] py-4 text-base font-extrabold text-tbh24b-ink bg-tbh24b-acc focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -ml-1 -mt-1 w-2 h-2 rounded-full bg-transparent opacity-0 scale-[.3] pointer-events-none shadow-[0_-34px_var(--color-tbh24b-acc),24px_-24px_var(--color-tbh24b-acc),34px_0_var(--color-tbh24b-acc),24px_24px_var(--color-tbh24b-acc),0_34px_var(--color-tbh24b-acc),-24px_24px_var(--color-tbh24b-acc),-34px_0_var(--color-tbh24b-acc),-24px_-24px_var(--color-tbh24b-acc)] group-hover/btn:animate-tbh24b-spark group-focus-visible/btn:animate-tbh24b-spark motion-reduce:group-hover/btn:animate-none"></span>
<span class="relative z-[1]">Boost</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh24a-acc: #e0492f;
--color-tbh24a-acc: oklch(0.62 0.22 20);
--color-tbh24a-p1: #ff7ab6;
--color-tbh24a-p1: oklch(0.78 0.16 350);
--color-tbh24a-p2: #7ce0ff;
--color-tbh24a-p2: oklch(0.87 0.11 215);
--color-tbh24a-p3: #b6ff7a;
--color-tbh24a-p3: oklch(0.92 0.18 130);
--color-tbh24a-p4: #ffd15c;
--color-tbh24a-p4: oklch(0.87 0.15 90);
--color-tbh24a-stage: #120a0e;
--color-tbh24a-stage: oklch(0.15 0.02 20);
--color-tbh24b-acc: #e0b04a;
--color-tbh24b-acc: oklch(0.78 0.15 90);
--color-tbh24b-ink: #1a1206;
--color-tbh24b-ink: oklch(0.22 0.04 85);
--color-tbh24b-stage: #14100a;
--color-tbh24b-stage: oklch(0.16 0.02 85);
--font-tbh24-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh24a-burst: tbh24a-burst 0.6s ease-out;
--animate-tbh24b-spark: tbh24b-spark 0.55s ease-out;
}
/* Stays CSS: two one-shot keyframes. Both hold the -50% centring in translate and animate
only scale and opacity, so the utilities and the keyframe never fight over a property. */
@keyframes tbh24a-burst {
0% {
opacity: 1;
translate: -50% -50%;
scale: 0.2;
}
100% {
opacity: 0;
translate: -50% -50%;
scale: 1.6;
}
}
@keyframes tbh24b-spark {
0% {
opacity: 1;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.5;
}
}
.tbh-24-group {
width: 100%;
min-height: 100svh;
}
.tbh-24a,
.tbh-24b {
min-height: 100svh;
}
```How this works
One element, many particles. A box-shadow list renders arbitrarily many copies of an element's shape at arbitrary offsets and colours. Scaling the element scales every clone away from the origin at once, which is the entire burst — no per-particle animation, no JS, no extra nodes.
The animation is one-shot. No infinite: the keyframe runs once per hover, which is what makes it feel like a reaction rather than an ambience. Re-hovering replays it because the animation is applied by the hover variant.
Overflow must stay visible. This is the one demo in the collection that explicitly does NOT clip: overflow-visible lets the particles leave the button. Everything else about the pattern depends on that.
Make it yours
- Particle count: extend the
shadow-[...]clone list — each entry is one more particle at no DOM cost. - Palette: the four
--color-tbh24a-p*tokens; varying hue AND offset is what keeps it from looking mechanical. - Reach: the
scale-[1.6]end stop in the burst keyframe. - Trigger on click instead: move the animation from
group-hover/btn:togroup-active/btn:for a like-button feel.
Gotchas — read before shipping
- Keep the clone count sensible. Each one is a real paint; a 30-particle burst on a list of buttons will show up in INP.
- Particles need
pointer-events-none— they extend past the button and would otherwise create dead click zones around it. overflow-visibleon the button is required, which means this effect cannot be combined with any of the clipped fills in this collection.- Use a one-shot animation, not
infinite. A looping confetti burst reads as a broken loading state.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Multi-value box-shadow and keyframes are universal; with animation unavailable the particles simply never appear (they rest at opacity-0).