28 CSS Close Buttons19 / 28

Pure CSSMIT licensed

Particle Burst Close Button

A confetti-cannon dismiss: pressing the button detonates eight particles that fly outward on individual vectors, shrink, and fade — every particle is a box-shadow point on ONE pseudo-element, so the whole explosion costs a single node and a single keyframe. The classic checkbox-burst trick, weaponised for close buttons.

Published Updated

Live Demo
Try it

The code

<section class="ccb-19" aria-label="Particle burst close button demo">
  <button class="ccb-19__close" type="button" aria-label="Close">
    <span class="ccb-19__x" aria-hidden="true"></span>
    <span class="ccb-19__burst" aria-hidden="true"></span>
  </button>
</section>
.ccb-19,
.ccb-19 *,
.ccb-19 *::before,
.ccb-19 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ccb-19 {
  --p1: oklch(0.75 0.18 60);
  --p2: oklch(0.65 0.22 330);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(180deg,oklch(0.2 0.03 300),oklch(0.15 0.02 290));
}

.ccb-19 .ccb-19__close {
  position: relative;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border: 1.5px solid oklch(0.75 0.18 60 / .4);
  border-radius: 50%;
  background: oklch(0.24 0.035 300);
  cursor: pointer;
  transition: border-color .2s ease;
  box-shadow: 0 16px 36px -18px rgba(0,0,0,.8);
}

.ccb-19 .ccb-19__close:hover {
  border-color: var(--p1);
}

.ccb-19 .ccb-19__x {
  position: relative;
  width: 20px;
  height: 20px;
  transition: transform .2s ease;
}

.ccb-19 .ccb-19__x::before,
.ccb-19 .ccb-19__x::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 2.5px;
  border-radius: 2px;
  background: oklch(0.95 0.02 60);
}

.ccb-19 .ccb-19__x::before {
  transform: translate(-50%,-50%) rotate(45deg);
}

.ccb-19 .ccb-19__x::after {
  transform: translate(-50%,-50%) rotate(-45deg);
}

.ccb-19 .ccb-19__burst {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px;
  border-radius: 50%;
  background: transparent;
  pointer-events: none;
}

.ccb-19 .ccb-19__close:active .ccb-19__burst {
  animation: ccb-19-boom .55s cubic-bezier(.2,.7,.3,1) forwards;
}

.ccb-19 .ccb-19__close:active .ccb-19__x {
  animation: ccb-19-pop .55s cubic-bezier(.34,1.56,.64,1) forwards;
}

@keyframes ccb-19-boom {
  0% {
    background: var(--p1);
    box-shadow: 0 0 0 0 var(--p1),0 0 0 0 var(--p2),0 0 0 0 var(--p1),0 0 0 0 var(--p2),0 0 0 0 var(--p1),0 0 0 0 var(--p2),0 0 0 0 var(--p1),0 0 0 0 var(--p2);
  }

  100% {
    background: transparent;
    box-shadow: 26px 0 0 -3px transparent,18px -18px 0 -3px transparent,0 -26px 0 -3px transparent,-18px -18px 0 -3px transparent,-26px 0 0 -3px transparent,-18px 18px 0 -3px transparent,0 26px 0 -3px transparent,18px 18px 0 -3px transparent;
  }

  60% {
    background: transparent;
    box-shadow: 22px 0 0 0 var(--p1),15px -15px 0 0 var(--p2),0 -22px 0 0 var(--p1),-15px -15px 0 0 var(--p2),-22px 0 0 0 var(--p1),-15px 15px 0 0 var(--p2),0 22px 0 0 var(--p1),15px 15px 0 0 var(--p2);
  }
}

@keyframes ccb-19-pop {
  0% {
    transform: scale(1);
  }

  30% {
    transform: scale(.8);
  }

  70% {
    transform: scale(1.12);
  }

  100% {
    transform: scale(1);
  }
}

.ccb-19 .ccb-19__close:focus-visible {
  outline: 3px solid var(--p1);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .ccb-19 .ccb-19__close:active .ccb-19__burst,
    .ccb-19 .ccb-19__close:active .ccb-19__x {
    animation: none;
  }
}
/* No JavaScript — eight box-shadow copies of one 6px dot ARE the particles; a single keyframe flings them outward and shrinks them. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Close 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: Particle Burst Close Button
Source: https://codefronts.com/components/css-close-buttons/particle-burst/

A confetti-cannon dismiss: pressing the button detonates eight particles that fly outward on individual vectors, shrink, and fade — every particle is a box-shadow point on ONE pseudo-element, so the whole explosion costs a single node and a single keyframe. The classic checkbox-burst trick, weaponised for close buttons.
## HTML
```html
<section class="ccb-19" aria-label="Particle burst close button demo">
  <button class="ccb-19__close" type="button" aria-label="Close">
    <span class="ccb-19__x" aria-hidden="true"></span>
    <span class="ccb-19__burst" aria-hidden="true"></span>
  </button>
</section>
```
## CSS
```css
.ccb-19,
.ccb-19 *,
.ccb-19 *::before,
.ccb-19 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ccb-19 {
  --p1: oklch(0.75 0.18 60);
  --p2: oklch(0.65 0.22 330);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(180deg,oklch(0.2 0.03 300),oklch(0.15 0.02 290));
}

.ccb-19 .ccb-19__close {
  position: relative;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border: 1.5px solid oklch(0.75 0.18 60 / .4);
  border-radius: 50%;
  background: oklch(0.24 0.035 300);
  cursor: pointer;
  transition: border-color .2s ease;
  box-shadow: 0 16px 36px -18px rgba(0,0,0,.8);
}

.ccb-19 .ccb-19__close:hover {
  border-color: var(--p1);
}

.ccb-19 .ccb-19__x {
  position: relative;
  width: 20px;
  height: 20px;
  transition: transform .2s ease;
}

.ccb-19 .ccb-19__x::before,
.ccb-19 .ccb-19__x::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 2.5px;
  border-radius: 2px;
  background: oklch(0.95 0.02 60);
}

.ccb-19 .ccb-19__x::before {
  transform: translate(-50%,-50%) rotate(45deg);
}

.ccb-19 .ccb-19__x::after {
  transform: translate(-50%,-50%) rotate(-45deg);
}

.ccb-19 .ccb-19__burst {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px;
  border-radius: 50%;
  background: transparent;
  pointer-events: none;
}

.ccb-19 .ccb-19__close:active .ccb-19__burst {
  animation: ccb-19-boom .55s cubic-bezier(.2,.7,.3,1) forwards;
}

.ccb-19 .ccb-19__close:active .ccb-19__x {
  animation: ccb-19-pop .55s cubic-bezier(.34,1.56,.64,1) forwards;
}

@keyframes ccb-19-boom {
  0% {
    background: var(--p1);
    box-shadow: 0 0 0 0 var(--p1),0 0 0 0 var(--p2),0 0 0 0 var(--p1),0 0 0 0 var(--p2),0 0 0 0 var(--p1),0 0 0 0 var(--p2),0 0 0 0 var(--p1),0 0 0 0 var(--p2);
  }

  100% {
    background: transparent;
    box-shadow: 26px 0 0 -3px transparent,18px -18px 0 -3px transparent,0 -26px 0 -3px transparent,-18px -18px 0 -3px transparent,-26px 0 0 -3px transparent,-18px 18px 0 -3px transparent,0 26px 0 -3px transparent,18px 18px 0 -3px transparent;
  }

  60% {
    background: transparent;
    box-shadow: 22px 0 0 0 var(--p1),15px -15px 0 0 var(--p2),0 -22px 0 0 var(--p1),-15px -15px 0 0 var(--p2),-22px 0 0 0 var(--p1),-15px 15px 0 0 var(--p2),0 22px 0 0 var(--p1),15px 15px 0 0 var(--p2);
  }
}

@keyframes ccb-19-pop {
  0% {
    transform: scale(1);
  }

  30% {
    transform: scale(.8);
  }

  70% {
    transform: scale(1.12);
  }

  100% {
    transform: scale(1);
  }
}

.ccb-19 .ccb-19__close:focus-visible {
  outline: 3px solid var(--p1);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .ccb-19 .ccb-19__close:active .ccb-19__burst,
    .ccb-19 .ccb-19__close:active .ccb-19__x {
    animation: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — eight box-shadow copies of one 6px dot ARE the particles; a single keyframe flings them outward and shrinks them. */
```

How this works

The ::after is a 6px dot whose box-shadow lists eight offset copies — one shadow per particle, each with its own x/y offset and color. The burst keyframe animates the offsets outward: at 0% every shadow sits at 0 0; at 100% each reaches its final vector with 0 spread shrinking the points to nothing. One property animates eight particles.

The detonation triggers on :active and completes via animation-fill-mode even if the press is brief. The × does a sympathetic squash-and-pop (scale .8 → 1.1 → 1) timed to the explosion's first 60%. Particle colors alternate between two oklch accents, and the whole rig is prefers-reduced-motion-gated to a simple color flash.

Make it yours

  • Particle count = the number of shadows in the keyframe (8 here; 12 reads festive, 5 minimal).
  • Spread radius via the offset magnitudes (±26px here).
  • Recolor the confetti with the two accent variables.
  • Make it hover-triggered for preview surfaces (keep :active for real dismissals — bursts should be earned).

Gotchas — read before shipping

  • Box-shadow animation repaints — fine for one small 6px dot per press, deadly if you attach it to hover on a grid of 50 buttons.
  • The dot itself must end transparent (background fades in the keyframe) or a ninth phantom particle lingers at centre.
  • Give the animation ≥.5s; particle physics under 400ms reads as flicker, not explosion.

Browser support

ChromeSafariFirefoxEdge
111+16.4+113+111+

Animated box-shadow works everywhere — this is the one demo where paint cost is the accepted trade for a single-node effect.

Techniques used in this demo

Search CodeFronts

Loading…