25 CSS Play / Pause Buttons06 / 25

Pure CSSMIT licensed

Cyberpunk Glow & Neon Toggle

Synthwave HUD energy without a single image: the glow is stacked box-shadows, the halo is a blurred pseudo-element clone, the scanlines are a repeating-linear-gradient, and the whole rig cranks up a stop when playback starts. The trick that keeps it from looking cheap is treating light like light — a tight inner bloom, a wide soft falloff, and a surface tint, all derived from one accent hue.

Published

Live Demo
Try it

The code

<section class="pp-06" aria-label="Cyberpunk neon glow play pause toggle demo">
  <div class="pp-06__stage">
    <div class="pp-06__rig">
      <p class="pp-06__hud" aria-hidden="true">SIG//7734 · BITRATE 320 · NEO-KYOTO FM</p>
      <input class="pp-06-chk" type="checkbox" id="pp-06-toggle" aria-label="Play or pause the stream">
      <label class="pp-06__btn" for="pp-06-toggle">
        <span class="pp-06__halo" aria-hidden="true"></span>
        <svg viewBox="0 0 24 24" width="30" height="30" aria-hidden="true">
          <path class="pp-06__p" d="M8.8 5.4v13.2L19 12z" fill="currentColor"/>
          <path class="pp-06__q" d="M8 5h3.2v14H8zM12.8 5H16v14h-3.2z" fill="currentColor"/>
        </svg>
      </label>
      <p class="pp-06__state" aria-hidden="true"><span class="pp-06__on">▮ STREAM LIVE</span><span class="pp-06__off">▷ STANDBY</span></p>
    </div>
    <p class="pp-06__chip" aria-hidden="true">stacked box-shadow bloom · blurred ::before halo · CRT scanlines</p>
  </div>
</section>
.pp-06,
.pp-06 *,
.pp-06 *::before,
.pp-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.pp-06 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--pp-06-bg);
  --pp-06-bg: oklch(0.14 0.03 285);
  --pp-06-ink: oklch(0.95 0.02 200);
  --pp-06-mut: oklch(0.68 0.05 285);
  --pp-06-neon: oklch(0.82 0.18 195);
  --pp-06-neon2: oklch(0.7 0.24 340);
  font-family: ui-monospace,'SFMono-Regular',Menlo,Consolas,monospace;
  color: var(--pp-06-ink);
}

.pp-06__stage {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 30px;
  padding: clamp(20px,5vw,56px);
  background: radial-gradient(60% 45% at 50% 42%,oklch(0.26 0.1 300/.75),transparent 70%),linear-gradient(180deg,var(--pp-06-bg),oklch(0.1 0.03 290));
}

.pp-06__stage::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(to bottom,oklch(0 0 0/.28) 0 1px,transparent 1px 3px);
  mix-blend-mode: multiply;
}

.pp-06__rig {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 26px;
  padding: 38px 44px;
  border: 1px solid color-mix(in oklch,var(--pp-06-neon) 34%,transparent);
  border-radius: 20px;
  background: oklch(0.17 0.04 288/.7);
  box-shadow: 0 0 0 1px oklch(0 0 0/.5),0 0 40px oklch(0.5 0.15 300/.25),inset 0 0 60px oklch(0.3 0.1 300/.25);
}

.pp-06__hud {
  font-size: 11px;
  letter-spacing: .24em;
  color: var(--pp-06-mut);
  text-shadow: 0 0 8px color-mix(in oklch,var(--pp-06-neon2) 60%,transparent);
}

.pp-06-chk {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  clip-path: inset(50%);
}

.pp-06__btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 120px;
  height: 120px;
  border: 2px solid var(--pp-06-neon);
  border-radius: 50%;
  color: var(--pp-06-neon);
  background: oklch(0.18 0.04 250/.5);
  cursor: pointer;
  box-shadow: 0 0 6px color-mix(in oklch,var(--pp-06-neon) 70%,transparent),0 0 22px color-mix(in oklch,var(--pp-06-neon) 45%,transparent),0 0 60px color-mix(in oklch,var(--pp-06-neon) 20%,transparent),inset 0 0 14px color-mix(in oklch,var(--pp-06-neon) 22%,transparent);
  transition: color .35s,border-color .35s,box-shadow .35s,transform .25s cubic-bezier(.34,1.4,.5,1);
}

.pp-06__btn:hover {
  transform: scale(1.05);
}

.pp-06__btn:active {
  transform: scale(.96);
}

.pp-06-chk:focus-visible+.pp-06__btn {
  outline: 2px dashed var(--pp-06-neon2);
  outline-offset: 8px;
}

.pp-06__halo {
  position: absolute;
  inset: -26%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle,color-mix(in oklch,var(--pp-06-neon) 55%,transparent),transparent 62%);
  filter: blur(26px);
  opacity: .7;
  transition: opacity .4s,scale .5s cubic-bezier(.34,1.3,.5,1);
}

.pp-06__q {
  display: none;
}

.pp-06-chk:checked+.pp-06__btn {
  color: var(--pp-06-neon2);
  border-color: var(--pp-06-neon2);
  box-shadow: 0 0 8px color-mix(in oklch,var(--pp-06-neon2) 75%,transparent),0 0 30px color-mix(in oklch,var(--pp-06-neon2) 50%,transparent),0 0 82px color-mix(in oklch,var(--pp-06-neon2) 28%,transparent),inset 0 0 20px color-mix(in oklch,var(--pp-06-neon2) 26%,transparent);
}

.pp-06-chk:checked+.pp-06__btn .pp-06__halo {
  opacity: 1;
  scale: 1.3;
  background: radial-gradient(circle,color-mix(in oklch,var(--pp-06-neon2) 60%,transparent),transparent 62%);
}

.pp-06-chk:checked+.pp-06__btn .pp-06__p {
  display: none;
}

.pp-06-chk:checked+.pp-06__btn .pp-06__q {
  display: block;
}

.pp-06__state {
  font-size: 12.5px;
  letter-spacing: .18em;
}

.pp-06__on {
  display: none;
  color: var(--pp-06-neon2);
  text-shadow: 0 0 12px currentColor;
  animation: pp-06-flicker 3.4s steps(1,end) infinite;
}

.pp-06__off {
  color: var(--pp-06-neon);
  text-shadow: 0 0 10px currentColor;
}

.pp-06__rig:has(.pp-06-chk:checked) .pp-06__on {
  display: inline;
}

.pp-06__rig:has(.pp-06-chk:checked) .pp-06__off {
  display: none;
}

@keyframes pp-06-flicker {
  0%,
    92%,
    100% {
    opacity: 1;
  }

  93% {
    opacity: .35;
  }

  94% {
    opacity: 1;
  }

  96% {
    opacity: .55;
  }

  97% {
    opacity: 1;
  }
}

.pp-06__chip {
  position: relative;
  font-size: 11.5px;
  color: var(--pp-06-mut);
  padding: 7px 14px;
  border: 1px solid color-mix(in oklch,var(--pp-06-neon) 25%,transparent);
  border-radius: 99px;
  text-align: center;
}

@media (forced-colors: active) {
  .pp-06__btn {
    border: 2px solid ButtonText;
    color: ButtonText;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pp-06 * {
    animation: none !important;
    transition-duration: .01ms !important;
  }
}
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 Play / Pause 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: Cyberpunk Glow & Neon Toggle
Source: https://codefronts.com/components/css-play-pause-buttons/cyberpunk-glow-and-neon-toggle/

Synthwave HUD energy without a single image: the glow is stacked box-shadows, the halo is a blurred pseudo-element clone, the scanlines are a repeating-linear-gradient, and the whole rig cranks up a stop when playback starts. The trick that keeps it from looking cheap is treating light like light — a tight inner bloom, a wide soft falloff, and a surface tint, all derived from one accent hue.
## HTML
```html
<section class="pp-06" aria-label="Cyberpunk neon glow play pause toggle demo">
  <div class="pp-06__stage">
    <div class="pp-06__rig">
      <p class="pp-06__hud" aria-hidden="true">SIG//7734 · BITRATE 320 · NEO-KYOTO FM</p>
      <input class="pp-06-chk" type="checkbox" id="pp-06-toggle" aria-label="Play or pause the stream">
      <label class="pp-06__btn" for="pp-06-toggle">
        <span class="pp-06__halo" aria-hidden="true"></span>
        <svg viewBox="0 0 24 24" width="30" height="30" aria-hidden="true">
          <path class="pp-06__p" d="M8.8 5.4v13.2L19 12z" fill="currentColor"/>
          <path class="pp-06__q" d="M8 5h3.2v14H8zM12.8 5H16v14h-3.2z" fill="currentColor"/>
        </svg>
      </label>
      <p class="pp-06__state" aria-hidden="true"><span class="pp-06__on">▮ STREAM LIVE</span><span class="pp-06__off">▷ STANDBY</span></p>
    </div>
    <p class="pp-06__chip" aria-hidden="true">stacked box-shadow bloom · blurred ::before halo · CRT scanlines</p>
  </div>
</section>
```
## CSS
```css
.pp-06,
.pp-06 *,
.pp-06 *::before,
.pp-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.pp-06 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--pp-06-bg);
  --pp-06-bg: oklch(0.14 0.03 285);
  --pp-06-ink: oklch(0.95 0.02 200);
  --pp-06-mut: oklch(0.68 0.05 285);
  --pp-06-neon: oklch(0.82 0.18 195);
  --pp-06-neon2: oklch(0.7 0.24 340);
  font-family: ui-monospace,'SFMono-Regular',Menlo,Consolas,monospace;
  color: var(--pp-06-ink);
}

.pp-06__stage {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 30px;
  padding: clamp(20px,5vw,56px);
  background: radial-gradient(60% 45% at 50% 42%,oklch(0.26 0.1 300/.75),transparent 70%),linear-gradient(180deg,var(--pp-06-bg),oklch(0.1 0.03 290));
}

.pp-06__stage::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(to bottom,oklch(0 0 0/.28) 0 1px,transparent 1px 3px);
  mix-blend-mode: multiply;
}

.pp-06__rig {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 26px;
  padding: 38px 44px;
  border: 1px solid color-mix(in oklch,var(--pp-06-neon) 34%,transparent);
  border-radius: 20px;
  background: oklch(0.17 0.04 288/.7);
  box-shadow: 0 0 0 1px oklch(0 0 0/.5),0 0 40px oklch(0.5 0.15 300/.25),inset 0 0 60px oklch(0.3 0.1 300/.25);
}

.pp-06__hud {
  font-size: 11px;
  letter-spacing: .24em;
  color: var(--pp-06-mut);
  text-shadow: 0 0 8px color-mix(in oklch,var(--pp-06-neon2) 60%,transparent);
}

.pp-06-chk {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  clip-path: inset(50%);
}

.pp-06__btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 120px;
  height: 120px;
  border: 2px solid var(--pp-06-neon);
  border-radius: 50%;
  color: var(--pp-06-neon);
  background: oklch(0.18 0.04 250/.5);
  cursor: pointer;
  box-shadow: 0 0 6px color-mix(in oklch,var(--pp-06-neon) 70%,transparent),0 0 22px color-mix(in oklch,var(--pp-06-neon) 45%,transparent),0 0 60px color-mix(in oklch,var(--pp-06-neon) 20%,transparent),inset 0 0 14px color-mix(in oklch,var(--pp-06-neon) 22%,transparent);
  transition: color .35s,border-color .35s,box-shadow .35s,transform .25s cubic-bezier(.34,1.4,.5,1);
}

.pp-06__btn:hover {
  transform: scale(1.05);
}

.pp-06__btn:active {
  transform: scale(.96);
}

.pp-06-chk:focus-visible+.pp-06__btn {
  outline: 2px dashed var(--pp-06-neon2);
  outline-offset: 8px;
}

.pp-06__halo {
  position: absolute;
  inset: -26%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle,color-mix(in oklch,var(--pp-06-neon) 55%,transparent),transparent 62%);
  filter: blur(26px);
  opacity: .7;
  transition: opacity .4s,scale .5s cubic-bezier(.34,1.3,.5,1);
}

.pp-06__q {
  display: none;
}

.pp-06-chk:checked+.pp-06__btn {
  color: var(--pp-06-neon2);
  border-color: var(--pp-06-neon2);
  box-shadow: 0 0 8px color-mix(in oklch,var(--pp-06-neon2) 75%,transparent),0 0 30px color-mix(in oklch,var(--pp-06-neon2) 50%,transparent),0 0 82px color-mix(in oklch,var(--pp-06-neon2) 28%,transparent),inset 0 0 20px color-mix(in oklch,var(--pp-06-neon2) 26%,transparent);
}

.pp-06-chk:checked+.pp-06__btn .pp-06__halo {
  opacity: 1;
  scale: 1.3;
  background: radial-gradient(circle,color-mix(in oklch,var(--pp-06-neon2) 60%,transparent),transparent 62%);
}

.pp-06-chk:checked+.pp-06__btn .pp-06__p {
  display: none;
}

.pp-06-chk:checked+.pp-06__btn .pp-06__q {
  display: block;
}

.pp-06__state {
  font-size: 12.5px;
  letter-spacing: .18em;
}

.pp-06__on {
  display: none;
  color: var(--pp-06-neon2);
  text-shadow: 0 0 12px currentColor;
  animation: pp-06-flicker 3.4s steps(1,end) infinite;
}

.pp-06__off {
  color: var(--pp-06-neon);
  text-shadow: 0 0 10px currentColor;
}

.pp-06__rig:has(.pp-06-chk:checked) .pp-06__on {
  display: inline;
}

.pp-06__rig:has(.pp-06-chk:checked) .pp-06__off {
  display: none;
}

@keyframes pp-06-flicker {
  0%,
    92%,
    100% {
    opacity: 1;
  }

  93% {
    opacity: .35;
  }

  94% {
    opacity: 1;
  }

  96% {
    opacity: .55;
  }

  97% {
    opacity: 1;
  }
}

.pp-06__chip {
  position: relative;
  font-size: 11.5px;
  color: var(--pp-06-mut);
  padding: 7px 14px;
  border: 1px solid color-mix(in oklch,var(--pp-06-neon) 25%,transparent);
  border-radius: 99px;
  text-align: center;
}

@media (forced-colors: active) {
  .pp-06__btn {
    border: 2px solid ButtonText;
    color: ButtonText;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pp-06 * {
    animation: none !important;
    transition-duration: .01ms !important;
  }
}
```

How this works

Neon is never one shadow. It is a stack, from tightest to widest, plus a matching inset so the tube itself looks lit:

.btn{
  border:2px solid var(--neon);
  color:var(--neon);
  box-shadow:
    0 0 6px  color-mix(in oklch, var(--neon) 70%, transparent),  /* filament */
    0 0 22px color-mix(in oklch, var(--neon) 45%, transparent),  /* bloom    */
    0 0 60px color-mix(in oklch, var(--neon) 22%, transparent),  /* falloff  */
    inset 0 0 14px color-mix(in oklch, var(--neon) 25%, transparent);
  text-shadow:0 0 10px currentColor;
}

The atmospheric halo behind the button is a separate layer — a ::before filled with a radial-gradient and filter:blur(26px), sitting at a negative z-index. Blur on a real element produces a softer, more physical falloff than any shadow can, and because it is a sibling layer you can scale it independently: on :checked it grows to 1.35 and brightens, so the bloom breathes when the track plays.

Two finishing textures: a repeating-linear-gradient of 1px dark lines every 3px lays CRT scanlines over the panel, and a short pp-06-flicker keyframe with irregular opacity steps runs on the label — real neon signs are never perfectly steady. Both are decorative, both are disabled under prefers-reduced-motion.

Make it yours

  • Hue shift on play: animate filter:hue-rotate() on the wrapper for a magenta→cyan sweep — one line, huge payoff on dark UIs.
  • Tube-off state: drop the shadows to 0 0 2px and set color to a dim gray for a convincing 'unpowered' look before first interaction.
  • Chromatic aberration: duplicate the label in a ::after offset by 1px with a contrasting hue and mix-blend-mode:screen for a glitchy RGB split.
  • Softer sci-fi (less arcade): keep the falloff shadow, delete the flicker and scanlines, and raise the surface lightness — same rig reads as premium rather than retro.

Gotchas — read before shipping

  • Neon only works on a dark, slightly desaturated surface. On white the bloom disappears and you are left with a fuzzy border.
  • Glowing text often fails contrast checks — keep body copy un-glowed and reserve the effect for large display type and icons (WCAG 1.4.3 exempts nothing, but bigger text needs only 3:1).
  • Very large blur radii repaint a lot of pixels. Animate transform/opacity of the blurred layer, never the blur radius itself.
  • Flicker animations can trigger discomfort; keep them subtle, above 3 Hz never, and always kill them under prefers-reduced-motion (WCAG 2.3.1).
  • Forced-colors mode strips shadows entirely — add a @media (forced-colors:active) block with a solid border so the control stays visible.

Browser support

ChromeSafariFirefoxEdge
111+16.4+113+111+

box-shadow, filter:blur() and repeating gradients are universal; oklch()/color-mix() set the modern floor. No JS, no :has() required.

Search CodeFronts

Loading…