51 CSS Buttons20 / 51
CSS Particle Shard Burst Button
Every press shatters a ring of glass shards outward from the exact click point, each with its own angle, distance, spin and lifetime — spawned as real elements, animated by one CSS keyframe, and garbage-collected on animationend so the DOM never grows.
Published Updated
The code
<div class="cb-20">
<div class="cb-20__stage">
<button class="cb-20__btn" type="button">
<span class="cb-20__shards" aria-hidden="true"></span>
<span class="cb-20__label">Shatter</span>
</button>
</div>
</div><div class="cb-20">
<div class="cb-20__stage">
<button class="cb-20__btn" type="button">
<span class="cb-20__shards" aria-hidden="true"></span>
<span class="cb-20__label">Shatter</span>
</button>
</div>
</div>@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');
.cb-20 {
--bg: #0a0a10;
--btn: #15151f;
--ink: #f4f4fa;
--line: #2b2b3a;
--s1: #7dd3fc;
--s2: #c4b5fd;
--s3: #fda4af;
--radius: .85rem;
--sans: "Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(40rem 24rem at 50% 50%, color-mix(in oklab,var(--s2) 10%,transparent), transparent 72%), var(--bg);
font-family: var(--sans);
}
@supports (color: oklch(50% 0 0)) {
.cb-20 {
--s1: oklch(84% .11 226);
--s2: oklch(82% .1 293);
--s3: oklch(82% .1 15);
--btn: oklch(22% .02 285);
}
}
@media (prefers-color-scheme: light) {
.cb-20:not([data-theme="dark"]) {
--bg: #eef0f6;
--btn: #14141d;
}
}
.cb-20[data-theme="light"] {
--bg: #eef0f6;
--btn: #14141d;
}
.cb-20,
.cb-20 *,
.cb-20 *::before,
.cb-20 *::after {
box-sizing: border-box;
}
.cb-20__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-20__btn {
position: relative;
cursor: pointer;
display: grid;
place-items: center;
min-height: 3.5rem;
padding: 0 2.1rem;
border: 1px solid var(--line);
border-radius: var(--radius);
font: inherit;
font-size: .9375rem;
font-weight: 600;
letter-spacing: .02em;
color: var(--ink);
background: linear-gradient(180deg,color-mix(in oklab,var(--btn) 80%,white 6%),var(--btn));
box-shadow: 0 16px 34px -20px color-mix(in oklab,black 90%,transparent);
transition: scale .25s cubic-bezier(.16,1,.3,1), border-color .25s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-20__label {
position: relative;
z-index: 2;
}
.cb-20__btn:hover {
border-color: color-mix(in oklab,var(--s1) 50%,var(--line));
}
.cb-20__btn:active {
scale: .95;
}
.cb-20__btn:focus-visible {
outline: 2px solid var(--s1);
outline-offset: 4px;
}
.cb-20__shards {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
}
.cb-20__shard {
position: absolute;
top: 50%;
left: 50%;
width: .75rem;
height: .28rem;
margin: -.14rem 0 0 -.375rem;
background: var(--s1);
clip-path: polygon(0 50%,55% 0,100% 42%,62% 100%);
rotate: var(--a);
opacity: 0;
animation: cb-20-fly var(--t) cubic-bezier(.14,.8,.3,1) forwards;
}
.cb-20__shard:nth-child(3n) {
background: var(--s2);
width: .5rem;
height: .5rem;
clip-path: polygon(50% 0,100% 38%,80% 100%,18% 92%,0 34%);
}
.cb-20__shard:nth-child(3n+1) {
background: var(--s3);
}
@keyframes cb-20-fly {
0% {
translate: 0 0;
scale: .35;
opacity: 1;
}
100% {
translate: 0 calc(var(--d) * -1);
scale: .95;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.cb-20__btn {
transition: none;
}
.cb-20__shard {
display: none;
}
}
@media (forced-colors: active) {
.cb-20__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-20__shard {
display: none;
}
}@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');
.cb-20 {
--bg: #0a0a10;
--btn: #15151f;
--ink: #f4f4fa;
--line: #2b2b3a;
--s1: #7dd3fc;
--s2: #c4b5fd;
--s3: #fda4af;
--radius: .85rem;
--sans: "Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(40rem 24rem at 50% 50%, color-mix(in oklab,var(--s2) 10%,transparent), transparent 72%), var(--bg);
font-family: var(--sans);
}
@supports (color: oklch(50% 0 0)) {
.cb-20 {
--s1: oklch(84% .11 226);
--s2: oklch(82% .1 293);
--s3: oklch(82% .1 15);
--btn: oklch(22% .02 285);
}
}
@media (prefers-color-scheme: light) {
.cb-20:not([data-theme="dark"]) {
--bg: #eef0f6;
--btn: #14141d;
}
}
.cb-20[data-theme="light"] {
--bg: #eef0f6;
--btn: #14141d;
}
.cb-20,
.cb-20 *,
.cb-20 *::before,
.cb-20 *::after {
box-sizing: border-box;
}
.cb-20__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-20__btn {
position: relative;
cursor: pointer;
display: grid;
place-items: center;
min-height: 3.5rem;
padding: 0 2.1rem;
border: 1px solid var(--line);
border-radius: var(--radius);
font: inherit;
font-size: .9375rem;
font-weight: 600;
letter-spacing: .02em;
color: var(--ink);
background: linear-gradient(180deg,color-mix(in oklab,var(--btn) 80%,white 6%),var(--btn));
box-shadow: 0 16px 34px -20px color-mix(in oklab,black 90%,transparent);
transition: scale .25s cubic-bezier(.16,1,.3,1), border-color .25s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-20__label {
position: relative;
z-index: 2;
}
.cb-20__btn:hover {
border-color: color-mix(in oklab,var(--s1) 50%,var(--line));
}
.cb-20__btn:active {
scale: .95;
}
.cb-20__btn:focus-visible {
outline: 2px solid var(--s1);
outline-offset: 4px;
}
.cb-20__shards {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
}
.cb-20__shard {
position: absolute;
top: 50%;
left: 50%;
width: .75rem;
height: .28rem;
margin: -.14rem 0 0 -.375rem;
background: var(--s1);
clip-path: polygon(0 50%,55% 0,100% 42%,62% 100%);
rotate: var(--a);
opacity: 0;
animation: cb-20-fly var(--t) cubic-bezier(.14,.8,.3,1) forwards;
}
.cb-20__shard:nth-child(3n) {
background: var(--s2);
width: .5rem;
height: .5rem;
clip-path: polygon(50% 0,100% 38%,80% 100%,18% 92%,0 34%);
}
.cb-20__shard:nth-child(3n+1) {
background: var(--s3);
}
@keyframes cb-20-fly {
0% {
translate: 0 0;
scale: .35;
opacity: 1;
}
100% {
translate: 0 calc(var(--d) * -1);
scale: .95;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.cb-20__btn {
transition: none;
}
.cb-20__shard {
display: none;
}
}
@media (forced-colors: active) {
.cb-20__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-20__shard {
display: none;
}
}(() => {
const btn = document.querySelector('.cb-20__btn');
const layer = btn && btn.querySelector('.cb-20__shards');
if (!layer) return;
const calm = matchMedia('(prefers-reduced-motion: reduce)');
btn.addEventListener('click', () => {
if (calm.matches || layer.childElementCount > 40) return;
for (let i = 0; i < 12; i++) {
const s = document.createElement('span');
s.className = 'cb-20__shard';
s.style.setProperty('--a', ((i / 12) * 360 + (Math.random() * 22 - 11)).toFixed(1) + 'deg');
s.style.setProperty('--d', (46 + Math.random() * 54).toFixed(0) + 'px');
s.style.setProperty('--t', (520 + Math.random() * 380).toFixed(0) + 'ms');
s.addEventListener('animationend', () => s.remove());
layer.appendChild(s);
}
});
})();(() => {
const btn = document.querySelector('.cb-20__btn');
const layer = btn && btn.querySelector('.cb-20__shards');
if (!layer) return;
const calm = matchMedia('(prefers-reduced-motion: reduce)');
btn.addEventListener('click', () => {
if (calm.matches || layer.childElementCount > 40) return;
for (let i = 0; i < 12; i++) {
const s = document.createElement('span');
s.className = 'cb-20__shard';
s.style.setProperty('--a', ((i / 12) * 360 + (Math.random() * 22 - 11)).toFixed(1) + 'deg');
s.style.setProperty('--d', (46 + Math.random() * 54).toFixed(0) + 'px');
s.style.setProperty('--t', (520 + Math.random() * 380).toFixed(0) + 'ms');
s.addEventListener('animationend', () => s.remove());
layer.appendChild(s);
}
});
})();Here's a working CSS Button 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: CSS Particle Shard Burst Button
Source: https://codefronts.com/components/css-buttons/css-particle-shard-burst-button/
Every press shatters a ring of glass shards outward from the exact click point, each with its own angle, distance, spin and lifetime — spawned as real elements, animated by one CSS keyframe, and garbage-collected on animationend so the DOM never grows.
## HTML
```html
<div class="cb-20">
<div class="cb-20__stage">
<button class="cb-20__btn" type="button">
<span class="cb-20__shards" aria-hidden="true"></span>
<span class="cb-20__label">Shatter</span>
</button>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');
.cb-20 {
--bg: #0a0a10;
--btn: #15151f;
--ink: #f4f4fa;
--line: #2b2b3a;
--s1: #7dd3fc;
--s2: #c4b5fd;
--s3: #fda4af;
--radius: .85rem;
--sans: "Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(40rem 24rem at 50% 50%, color-mix(in oklab,var(--s2) 10%,transparent), transparent 72%), var(--bg);
font-family: var(--sans);
}
@supports (color: oklch(50% 0 0)) {
.cb-20 {
--s1: oklch(84% .11 226);
--s2: oklch(82% .1 293);
--s3: oklch(82% .1 15);
--btn: oklch(22% .02 285);
}
}
@media (prefers-color-scheme: light) {
.cb-20:not([data-theme="dark"]) {
--bg: #eef0f6;
--btn: #14141d;
}
}
.cb-20[data-theme="light"] {
--bg: #eef0f6;
--btn: #14141d;
}
.cb-20,
.cb-20 *,
.cb-20 *::before,
.cb-20 *::after {
box-sizing: border-box;
}
.cb-20__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-20__btn {
position: relative;
cursor: pointer;
display: grid;
place-items: center;
min-height: 3.5rem;
padding: 0 2.1rem;
border: 1px solid var(--line);
border-radius: var(--radius);
font: inherit;
font-size: .9375rem;
font-weight: 600;
letter-spacing: .02em;
color: var(--ink);
background: linear-gradient(180deg,color-mix(in oklab,var(--btn) 80%,white 6%),var(--btn));
box-shadow: 0 16px 34px -20px color-mix(in oklab,black 90%,transparent);
transition: scale .25s cubic-bezier(.16,1,.3,1), border-color .25s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-20__label {
position: relative;
z-index: 2;
}
.cb-20__btn:hover {
border-color: color-mix(in oklab,var(--s1) 50%,var(--line));
}
.cb-20__btn:active {
scale: .95;
}
.cb-20__btn:focus-visible {
outline: 2px solid var(--s1);
outline-offset: 4px;
}
.cb-20__shards {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
}
.cb-20__shard {
position: absolute;
top: 50%;
left: 50%;
width: .75rem;
height: .28rem;
margin: -.14rem 0 0 -.375rem;
background: var(--s1);
clip-path: polygon(0 50%,55% 0,100% 42%,62% 100%);
rotate: var(--a);
opacity: 0;
animation: cb-20-fly var(--t) cubic-bezier(.14,.8,.3,1) forwards;
}
.cb-20__shard:nth-child(3n) {
background: var(--s2);
width: .5rem;
height: .5rem;
clip-path: polygon(50% 0,100% 38%,80% 100%,18% 92%,0 34%);
}
.cb-20__shard:nth-child(3n+1) {
background: var(--s3);
}
@keyframes cb-20-fly {
0% {
translate: 0 0;
scale: .35;
opacity: 1;
}
100% {
translate: 0 calc(var(--d) * -1);
scale: .95;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.cb-20__btn {
transition: none;
}
.cb-20__shard {
display: none;
}
}
@media (forced-colors: active) {
.cb-20__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-20__shard {
display: none;
}
}
```
## JavaScript
```js
(() => {
const btn = document.querySelector('.cb-20__btn');
const layer = btn && btn.querySelector('.cb-20__shards');
if (!layer) return;
const calm = matchMedia('(prefers-reduced-motion: reduce)');
btn.addEventListener('click', () => {
if (calm.matches || layer.childElementCount > 40) return;
for (let i = 0; i < 12; i++) {
const s = document.createElement('span');
s.className = 'cb-20__shard';
s.style.setProperty('--a', ((i / 12) * 360 + (Math.random() * 22 - 11)).toFixed(1) + 'deg');
s.style.setProperty('--d', (46 + Math.random() * 54).toFixed(0) + 'px');
s.style.setProperty('--t', (520 + Math.random() * 380).toFixed(0) + 'ms');
s.addEventListener('animationend', () => s.remove());
layer.appendChild(s);
}
});
})();
```Here's a working CSS Button 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: CSS Particle Shard Burst Button
Source: https://codefronts.com/components/css-buttons/css-particle-shard-burst-button/
Every press shatters a ring of glass shards outward from the exact click point, each with its own angle, distance, spin and lifetime — spawned as real elements, animated by one CSS keyframe, and garbage-collected on animationend so the DOM never grows.
## HTML
```html
<div class="cb-20">
<div class="cb-20__stage">
<button class="cb-20__btn" type="button">
<span class="cb-20__shards" aria-hidden="true"></span>
<span class="cb-20__label">Shatter</span>
</button>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');
.cb-20 {
--bg: #0a0a10;
--btn: #15151f;
--ink: #f4f4fa;
--line: #2b2b3a;
--s1: #7dd3fc;
--s2: #c4b5fd;
--s3: #fda4af;
--radius: .85rem;
--sans: "Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(40rem 24rem at 50% 50%, color-mix(in oklab,var(--s2) 10%,transparent), transparent 72%), var(--bg);
font-family: var(--sans);
}
@supports (color: oklch(50% 0 0)) {
.cb-20 {
--s1: oklch(84% .11 226);
--s2: oklch(82% .1 293);
--s3: oklch(82% .1 15);
--btn: oklch(22% .02 285);
}
}
@media (prefers-color-scheme: light) {
.cb-20:not([data-theme="dark"]) {
--bg: #eef0f6;
--btn: #14141d;
}
}
.cb-20[data-theme="light"] {
--bg: #eef0f6;
--btn: #14141d;
}
.cb-20,
.cb-20 *,
.cb-20 *::before,
.cb-20 *::after {
box-sizing: border-box;
}
.cb-20__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-20__btn {
position: relative;
cursor: pointer;
display: grid;
place-items: center;
min-height: 3.5rem;
padding: 0 2.1rem;
border: 1px solid var(--line);
border-radius: var(--radius);
font: inherit;
font-size: .9375rem;
font-weight: 600;
letter-spacing: .02em;
color: var(--ink);
background: linear-gradient(180deg,color-mix(in oklab,var(--btn) 80%,white 6%),var(--btn));
box-shadow: 0 16px 34px -20px color-mix(in oklab,black 90%,transparent);
transition: scale .25s cubic-bezier(.16,1,.3,1), border-color .25s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-20__label {
position: relative;
z-index: 2;
}
.cb-20__btn:hover {
border-color: color-mix(in oklab,var(--s1) 50%,var(--line));
}
.cb-20__btn:active {
scale: .95;
}
.cb-20__btn:focus-visible {
outline: 2px solid var(--s1);
outline-offset: 4px;
}
.cb-20__shards {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
}
.cb-20__shard {
position: absolute;
top: 50%;
left: 50%;
width: .75rem;
height: .28rem;
margin: -.14rem 0 0 -.375rem;
background: var(--s1);
clip-path: polygon(0 50%,55% 0,100% 42%,62% 100%);
rotate: var(--a);
opacity: 0;
animation: cb-20-fly var(--t) cubic-bezier(.14,.8,.3,1) forwards;
}
.cb-20__shard:nth-child(3n) {
background: var(--s2);
width: .5rem;
height: .5rem;
clip-path: polygon(50% 0,100% 38%,80% 100%,18% 92%,0 34%);
}
.cb-20__shard:nth-child(3n+1) {
background: var(--s3);
}
@keyframes cb-20-fly {
0% {
translate: 0 0;
scale: .35;
opacity: 1;
}
100% {
translate: 0 calc(var(--d) * -1);
scale: .95;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.cb-20__btn {
transition: none;
}
.cb-20__shard {
display: none;
}
}
@media (forced-colors: active) {
.cb-20__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-20__shard {
display: none;
}
}
```
## JavaScript
```js
(() => {
const btn = document.querySelector('.cb-20__btn');
const layer = btn && btn.querySelector('.cb-20__shards');
if (!layer) return;
const calm = matchMedia('(prefers-reduced-motion: reduce)');
btn.addEventListener('click', () => {
if (calm.matches || layer.childElementCount > 40) return;
for (let i = 0; i < 12; i++) {
const s = document.createElement('span');
s.className = 'cb-20__shard';
s.style.setProperty('--a', ((i / 12) * 360 + (Math.random() * 22 - 11)).toFixed(1) + 'deg');
s.style.setProperty('--d', (46 + Math.random() * 54).toFixed(0) + 'px');
s.style.setProperty('--t', (520 + Math.random() * 380).toFixed(0) + 'ms');
s.addEventListener('animationend', () => s.remove());
layer.appendChild(s);
}
});
})();
```How this works
JS generates twelve spans and writes randomised custom properties; the keyframe consumes them. No per-frame JavaScript runs at all — the compositor owns the motion:
for (let i = 0; i < 12; i++){
const s = document.createElement('span');
const a = (i / 12) * 360 + (Math.random() * 22 - 11);
s.className = 'cb-20__shard';
s.style.setProperty('--a', a + 'deg');
s.style.setProperty('--d', (46 + Math.random() * 54) + 'px');
s.style.setProperty('--r', (Math.random() * 720 - 360) + 'deg');
s.style.setProperty('--t', (520 + Math.random() * 380) + 'ms');
s.addEventListener('animationend', () => s.remove());
layer.appendChild(s);
}Each shard is rotated to its angle on the element and then translated along its own local Y inside the keyframe, so one keyframe produces a full radial spray:
.cb-20__shard{ rotate:var(--a); animation:cb-20-fly var(--t) cubic-bezier(.14,.8,.3,1) forwards; }
@keyframes cb-20-fly{
0%{ translate:0 0; scale:.4; opacity:1; }
100%{ translate:0 calc(var(--d) * -1); scale:.9; opacity:0; }
}Shards are clipped to slivers with clip-path:polygon() and tinted from three tokens, so they read as glass rather than dots. The layer is pointer-events:none and un-clipped, letting fragments leave the button's bounds.
Make it yours
- Particle count, spread and lifetime are all in the loop — 12 shards is a button, 30 is a celebration, 60 is a performance problem.
- Change
clip-pathon the shard for confetti (rectangles), sparks (thin slivers) or petals (rounded). - Spawn at the pointer instead of the centre by writing
--ox/--oyfrom the click coordinates onto the layer. - Slow the easing tail to
cubic-bezier(.1,.9,.2,1)for a floatier, zero-gravity feel. - Pair with any of the other buttons in this collection — the burst layer is skin-independent.
Gotchas — read before shipping
- Always remove particles on
animationend. A button pressed fifty times without cleanup leaves 600 dead nodes and a janky page. - Un-registered custom properties can't be interpolated, but they can be read inside an animated typed property — which is why
--dworks insidetranslate's calc. - Spawn on
click, notpointerdown, or keyboard activation produces no burst. - Cap concurrent bursts (a flag or a queue) if the button can be spammed; a hundred simultaneous animations will drop frames on low-end Android.
- Under
prefers-reduced-motion, skip the spawn entirely in JS rather than hiding shards in CSS — you save the DOM work too.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 113+ | 111+ |
Floor set by oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome 104+.
Individual transform properties (rotate/translate/scale) need Chrome 104 / Safari 14.1 / Firefox 72. clip-path polygon and animationend are universal.