47 CSS Toggles & Switches29 / 47

Pure CSSMIT licensed

Waveform Toggle

Static audio bars spring to life with staggered breathing animations when toggled on. The track border and indicator dot pulse with an ice-cyan glow. Audio-visual data physicality — toggle = sound on/off.

Published Updated

Live Demo
Try it

The code

<label class="tgl-29">
  <input class="tgl-29__input" type="checkbox" checked>
  <span class="tgl-29__body" aria-hidden="true">
    <span class="tgl-29__bars">
      <span></span><span></span><span></span><span></span><span></span>
      <span></span><span></span><span></span><span></span>
    </span>
    <span class="tgl-29__text">Microphone</span>
    <span class="tgl-29__dot"></span>
  </span>
</label>
.tgl-29 {
  --tgl-29-rim: #14141e;
  --tgl-29-wire: #1e1e2e;
  --tgl-29-fog: #3a3a52;
  --tgl-29-ash: #7a7a98;
  --tgl-29-ice: #00e5ff;
  font-family: "Inter", "Segoe UI", system-ui, sans-serif;
  font-size: 14px;
  min-height: 100vh;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 48px 20px;
  background: #14141e;
  box-sizing: border-box;
}

.tgl-29__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.tgl-29__body {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--tgl-29-rim);
  border: 1px solid var(--tgl-29-wire);
  min-width: 240px;
  transition: border-color 0.3s ease, background 0.3s ease;
}

.tgl-29__bars {
  display: flex;
  align-items: center;
  gap: 2.5px;
  height: 28px;
}

.tgl-29__bars span {
  display: block;
  width: 3px;
  border-radius: 2px;
  background: var(--tgl-29-fog);
  transition: background 0.3s ease, height 0.3s ease;
}
/* Static heights (off state) — each bar has its own resting height. */

.tgl-29__bars span:nth-child(1) {
  height: 8px;
}

.tgl-29__bars span:nth-child(2) {
  height: 14px;
}

.tgl-29__bars span:nth-child(3) {
  height: 20px;
}

.tgl-29__bars span:nth-child(4) {
  height: 26px;
}

.tgl-29__bars span:nth-child(5) {
  height: 18px;
}

.tgl-29__bars span:nth-child(6) {
  height: 24px;
}

.tgl-29__bars span:nth-child(7) {
  height: 12px;
}

.tgl-29__bars span:nth-child(8) {
  height: 20px;
}

.tgl-29__bars span:nth-child(9) {
  height: 8px;
}

.tgl-29__text {
  font-size: 13px;
  color: var(--tgl-29-ash);
  transition: color 0.3s ease;
}

.tgl-29__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tgl-29-wire);
  margin-left: auto;
  flex-shrink: 0;
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
/* Each bar oscillates between its resting height and ~30% of it. The
   varied durations + delays keep the pattern from looking robotic. */

@keyframes tgl-29-breathe {
  0%,
    100% {
    height: var(--tgl-29-h);
  }

  50% {
    height: calc(var(--tgl-29-h) * 0.3 + 4px);
  }
}

.tgl-29__input:checked ~ .tgl-29__body {
  border-color: rgba(0,229,255,0.3);
  background: rgba(0,229,255,0.04);
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span {
  background: var(--tgl-29-ice);
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(1) {
  animation: tgl-29-breathe 0.9s ease-in-out infinite;
  --tgl-29-h: 8px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(2) {
  animation: tgl-29-breathe 0.7s ease-in-out 0.1s infinite;
  --tgl-29-h: 14px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(3) {
  animation: tgl-29-breathe 1.1s ease-in-out 0.2s infinite;
  --tgl-29-h: 20px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(4) {
  animation: tgl-29-breathe 0.8s ease-in-out 0.05s infinite;
  --tgl-29-h: 26px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(5) {
  animation: tgl-29-breathe 1.0s ease-in-out 0.15s infinite;
  --tgl-29-h: 18px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(6) {
  animation: tgl-29-breathe 0.6s ease-in-out 0.25s infinite;
  --tgl-29-h: 24px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(7) {
  animation: tgl-29-breathe 0.9s ease-in-out 0.08s infinite;
  --tgl-29-h: 12px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(8) {
  animation: tgl-29-breathe 1.2s ease-in-out 0.18s infinite;
  --tgl-29-h: 20px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(9) {
  animation: tgl-29-breathe 0.75s ease-in-out 0.3s infinite;
  --tgl-29-h: 8px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__text {
  color: var(--tgl-29-ice);
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__dot {
  background: var(--tgl-29-ice);
  box-shadow: 0 0 8px var(--tgl-29-ice);
}

.tgl-29__input:focus-visible ~ .tgl-29__body {
  outline: 2px solid var(--tgl-29-ice);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .tgl-29__body,
    .tgl-29__bars span,
    .tgl-29__dot,
    .tgl-29__text {
    transition: none;
  }

  .tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span {
    animation: none;
  }
}
/* No JavaScript — one keyframe block reads a per-bar --h custom property, so nine staggered durations/delays make an ever-changing equalizer. */
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 Toggles & Switche 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: Waveform Toggle
Source: https://codefronts.com/components/css-toggles-switches/waveform-toggle/

Static audio bars spring to life with staggered breathing animations when toggled on. The track border and indicator dot pulse with an ice-cyan glow. Audio-visual data physicality — toggle = sound on/off.
## HTML
```html
<label class="tgl-29">
  <input class="tgl-29__input" type="checkbox" checked>
  <span class="tgl-29__body" aria-hidden="true">
    <span class="tgl-29__bars">
      <span></span><span></span><span></span><span></span><span></span>
      <span></span><span></span><span></span><span></span>
    </span>
    <span class="tgl-29__text">Microphone</span>
    <span class="tgl-29__dot"></span>
  </span>
</label>
```
## CSS
```css
.tgl-29 {
  --tgl-29-rim: #14141e;
  --tgl-29-wire: #1e1e2e;
  --tgl-29-fog: #3a3a52;
  --tgl-29-ash: #7a7a98;
  --tgl-29-ice: #00e5ff;
  font-family: "Inter", "Segoe UI", system-ui, sans-serif;
  font-size: 14px;
  min-height: 100vh;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 48px 20px;
  background: #14141e;
  box-sizing: border-box;
}

.tgl-29__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.tgl-29__body {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--tgl-29-rim);
  border: 1px solid var(--tgl-29-wire);
  min-width: 240px;
  transition: border-color 0.3s ease, background 0.3s ease;
}

.tgl-29__bars {
  display: flex;
  align-items: center;
  gap: 2.5px;
  height: 28px;
}

.tgl-29__bars span {
  display: block;
  width: 3px;
  border-radius: 2px;
  background: var(--tgl-29-fog);
  transition: background 0.3s ease, height 0.3s ease;
}
/* Static heights (off state) — each bar has its own resting height. */

.tgl-29__bars span:nth-child(1) {
  height: 8px;
}

.tgl-29__bars span:nth-child(2) {
  height: 14px;
}

.tgl-29__bars span:nth-child(3) {
  height: 20px;
}

.tgl-29__bars span:nth-child(4) {
  height: 26px;
}

.tgl-29__bars span:nth-child(5) {
  height: 18px;
}

.tgl-29__bars span:nth-child(6) {
  height: 24px;
}

.tgl-29__bars span:nth-child(7) {
  height: 12px;
}

.tgl-29__bars span:nth-child(8) {
  height: 20px;
}

.tgl-29__bars span:nth-child(9) {
  height: 8px;
}

.tgl-29__text {
  font-size: 13px;
  color: var(--tgl-29-ash);
  transition: color 0.3s ease;
}

.tgl-29__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tgl-29-wire);
  margin-left: auto;
  flex-shrink: 0;
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
/* Each bar oscillates between its resting height and ~30% of it. The
   varied durations + delays keep the pattern from looking robotic. */

@keyframes tgl-29-breathe {
  0%,
    100% {
    height: var(--tgl-29-h);
  }

  50% {
    height: calc(var(--tgl-29-h) * 0.3 + 4px);
  }
}

.tgl-29__input:checked ~ .tgl-29__body {
  border-color: rgba(0,229,255,0.3);
  background: rgba(0,229,255,0.04);
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span {
  background: var(--tgl-29-ice);
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(1) {
  animation: tgl-29-breathe 0.9s ease-in-out infinite;
  --tgl-29-h: 8px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(2) {
  animation: tgl-29-breathe 0.7s ease-in-out 0.1s infinite;
  --tgl-29-h: 14px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(3) {
  animation: tgl-29-breathe 1.1s ease-in-out 0.2s infinite;
  --tgl-29-h: 20px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(4) {
  animation: tgl-29-breathe 0.8s ease-in-out 0.05s infinite;
  --tgl-29-h: 26px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(5) {
  animation: tgl-29-breathe 1.0s ease-in-out 0.15s infinite;
  --tgl-29-h: 18px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(6) {
  animation: tgl-29-breathe 0.6s ease-in-out 0.25s infinite;
  --tgl-29-h: 24px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(7) {
  animation: tgl-29-breathe 0.9s ease-in-out 0.08s infinite;
  --tgl-29-h: 12px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(8) {
  animation: tgl-29-breathe 1.2s ease-in-out 0.18s infinite;
  --tgl-29-h: 20px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span:nth-child(9) {
  animation: tgl-29-breathe 0.75s ease-in-out 0.3s infinite;
  --tgl-29-h: 8px;
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__text {
  color: var(--tgl-29-ice);
}

.tgl-29__input:checked ~ .tgl-29__body .tgl-29__dot {
  background: var(--tgl-29-ice);
  box-shadow: 0 0 8px var(--tgl-29-ice);
}

.tgl-29__input:focus-visible ~ .tgl-29__body {
  outline: 2px solid var(--tgl-29-ice);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .tgl-29__body,
    .tgl-29__bars span,
    .tgl-29__dot,
    .tgl-29__text {
    transition: none;
  }

  .tgl-29__input:checked ~ .tgl-29__body .tgl-29__bars span {
    animation: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — one keyframe block reads a per-bar --h custom property, so nine staggered durations/delays make an ever-changing equalizer. */
```

How this works

Nine bars each carry a fixed resting height (8–26px) in the off state. When checked, every bar runs the same tgl-29-breathe keyframe — oscillating between its own resting height (passed in via the --tgl-29-h custom property) and ~30% of it — but with nine different duration/delay pairs, so the pattern never visibly repeats.

The one-keyframe-many-variables trick is the heart of this demo: the animation reads a per-element custom property, so a single @keyframes block powers nine unique motions. Alongside, the pill's border, label text and status dot all shift to ice-cyan via :checked ~ rules.

Make it yours

  • Add bars freely: give each a resting height, a --tgl-29-h and a unique duration/delay.
  • Recolor via --tgl-29-ice — border, bars, dot and text all reference it.
  • Slow all durations ×1.5 for an ambient, less busy feel.
  • Raise the 0.3 multiplier in the keyframe for shallower breathing.

Gotchas — read before shipping

  • This animates height — fine at 3px-wide bars, but transform:scaleY is the compositor-safe route if you enlarge the bars significantly.
  • Custom properties in keyframes resolve per element — that's what lets one keyframe serve nine bars; don't inline nine keyframe blocks.
  • Infinite loops must stop under prefers-reduced-motion (handled: bars freeze at resting heights).

Browser support

ChromeSafariFirefoxEdge
43+9.1+31+43+

Keyframes reading per-element custom properties; supported in all modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…