47 CSS Toggles & Switches09 / 47

Pure CSSMIT licensed

Waveform Mute Toggle

A mute switch that shows sound instead of describing it: five equalizer bars dance inside the pill while audio is live, then collapse to a flatline the instant you mute — state you can read from across the room, no thumb required.

Published

Live Demo
Try it

The code

<section class="tgl-09" aria-label="Waveform mute toggle">
  <label class="tgl-09__row" for="tgl-09-sw">
    <span class="tgl-09__lbl">Studio monitor</span>
    <span class="tgl-09__pill">
      <input type="checkbox" id="tgl-09-sw" class="tgl-09__sw" role="switch" checked>
      <span class="tgl-09__wave" aria-hidden="true"><i style="--d:0s"></i><i style="--d:.14s"></i><i style="--d:.28s"></i><i style="--d:.07s"></i><i style="--d:.21s"></i></span>
    </span>
  </label>
  <p class="tgl-09__cap">Live = dancing bars · muted = flatline</p>
</section>
.tgl-09,
.tgl-09 *,
.tgl-09 *::before,
.tgl-09 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tgl-09 {
  --live: oklch(0.78 0.16 160);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 60px 20px;
  background: #101318;
}

.tgl-09__row {
  display: flex;
  align-items: center;
  gap: 28px;
  cursor: pointer;
}

.tgl-09__lbl {
  font-size: 16px;
  font-weight: 600;
  color: #c4ccd8;
}

.tgl-09__pill {
  position: relative;
  width: 96px;
  height: 46px;
  border-radius: 26px;
  background: #1b2028;
  box-shadow: inset 0 2px 6px rgba(0,0,0,.6),inset 0 0 0 1px #262d38;
  transition: box-shadow .35s;
}

.tgl-09__pill:has(.tgl-09__sw:checked) {
  box-shadow: inset 0 2px 6px rgba(0,0,0,.6),inset 0 0 0 1px color-mix(in oklab,var(--live) 55%,transparent),0 0 18px -4px color-mix(in oklab,var(--live) 50%,transparent);
}

.tgl-09__sw {
  appearance: none;
  -webkit-appearance: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 26px;
  cursor: pointer;
  z-index: 1;
  background: transparent;
}

.tgl-09__wave {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  pointer-events: none;
}

.tgl-09__wave i {
  width: 5px;
  height: 26px;
  border-radius: 3px;
  background: #3d4654;
  transform: scaleY(.12);
  transform-origin: center;
  transition: background-color .3s,transform .3s;
}

.tgl-09__sw:checked ~ .tgl-09__wave i {
  background: var(--live);
  animation: tgl-09-eq 0.9s ease-in-out var(--d) infinite alternate;
}

.tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(2) {
  animation-duration: .75s;
}

.tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(4) {
  animation-duration: 1.05s;
}

.tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(5) {
  animation-duration: .8s;
}

@keyframes tgl-09-eq {
  0% {
    transform: scaleY(.25);
  }

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

.tgl-09__sw:focus-visible {
  outline: 3px solid var(--live);
  outline-offset: 4px;
}

.tgl-09__cap {
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #525a68;
}

@media (prefers-reduced-motion: reduce) {
  .tgl-09__sw:checked ~ .tgl-09__wave i {
    animation: none;
    transform: scaleY(.7);
  }

  .tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(odd) {
    transform: scaleY(.45);
  }
}
/* No JavaScript — five bars loop a staggered scaleY keyframe while checked and drop to a flatline via animation:none when muted. */
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 Mute Toggle
Source: https://codefronts.com/components/css-toggles-switches/waveform-mute-toggle/

A mute switch that shows sound instead of describing it: five equalizer bars dance inside the pill while audio is live, then collapse to a flatline the instant you mute — state you can read from across the room, no thumb required.
## HTML
```html
<section class="tgl-09" aria-label="Waveform mute toggle">
  <label class="tgl-09__row" for="tgl-09-sw">
    <span class="tgl-09__lbl">Studio monitor</span>
    <span class="tgl-09__pill">
      <input type="checkbox" id="tgl-09-sw" class="tgl-09__sw" role="switch" checked>
      <span class="tgl-09__wave" aria-hidden="true"><i style="--d:0s"></i><i style="--d:.14s"></i><i style="--d:.28s"></i><i style="--d:.07s"></i><i style="--d:.21s"></i></span>
    </span>
  </label>
  <p class="tgl-09__cap">Live = dancing bars · muted = flatline</p>
</section>
```
## CSS
```css
.tgl-09,
.tgl-09 *,
.tgl-09 *::before,
.tgl-09 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tgl-09 {
  --live: oklch(0.78 0.16 160);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 60px 20px;
  background: #101318;
}

.tgl-09__row {
  display: flex;
  align-items: center;
  gap: 28px;
  cursor: pointer;
}

.tgl-09__lbl {
  font-size: 16px;
  font-weight: 600;
  color: #c4ccd8;
}

.tgl-09__pill {
  position: relative;
  width: 96px;
  height: 46px;
  border-radius: 26px;
  background: #1b2028;
  box-shadow: inset 0 2px 6px rgba(0,0,0,.6),inset 0 0 0 1px #262d38;
  transition: box-shadow .35s;
}

.tgl-09__pill:has(.tgl-09__sw:checked) {
  box-shadow: inset 0 2px 6px rgba(0,0,0,.6),inset 0 0 0 1px color-mix(in oklab,var(--live) 55%,transparent),0 0 18px -4px color-mix(in oklab,var(--live) 50%,transparent);
}

.tgl-09__sw {
  appearance: none;
  -webkit-appearance: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 26px;
  cursor: pointer;
  z-index: 1;
  background: transparent;
}

.tgl-09__wave {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  pointer-events: none;
}

.tgl-09__wave i {
  width: 5px;
  height: 26px;
  border-radius: 3px;
  background: #3d4654;
  transform: scaleY(.12);
  transform-origin: center;
  transition: background-color .3s,transform .3s;
}

.tgl-09__sw:checked ~ .tgl-09__wave i {
  background: var(--live);
  animation: tgl-09-eq 0.9s ease-in-out var(--d) infinite alternate;
}

.tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(2) {
  animation-duration: .75s;
}

.tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(4) {
  animation-duration: 1.05s;
}

.tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(5) {
  animation-duration: .8s;
}

@keyframes tgl-09-eq {
  0% {
    transform: scaleY(.25);
  }

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

.tgl-09__sw:focus-visible {
  outline: 3px solid var(--live);
  outline-offset: 4px;
}

.tgl-09__cap {
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #525a68;
}

@media (prefers-reduced-motion: reduce) {
  .tgl-09__sw:checked ~ .tgl-09__wave i {
    animation: none;
    transform: scaleY(.7);
  }

  .tgl-09__sw:checked ~ .tgl-09__wave i:nth-child(odd) {
    transform: scaleY(.45);
  }
}
```

## JavaScript
```js
/* No JavaScript — five bars loop a staggered scaleY keyframe while checked and drop to a flatline via animation:none when muted. */
```

How this works

The pill is a checkbox overlay above five <i> bars. Each bar runs an infinite scaleY keyframe loop with a staggered animation-delay and slightly different duration, so the pattern never visibly repeats — a convincing waveform from 5 elements and one keyframe block.

Unchecked (muted), the bars get animation:none and transform:scaleY(.12), snapping to a flatline, and the pill desaturates. Because scaleY is compositor-driven, five infinite loops cost near-zero main-thread time — and the whole loop stops under prefers-reduced-motion, where checked state shows as static mid-height bars instead.

Make it yours

  • Add bars for a wider EQ — give each a new --d (delay) and the loop stays organic.
  • Recolor live vs muted via --live and the muted gray.
  • Slow all durations ×1.5 for a chill ambient feel.
  • Swap scaleY range (.2→1) for subtler motion.

Gotchas — read before shipping

  • Stop the loop with animation:none, not animation-play-state:paused — paused freezes bars mid-dance instead of flatlining.
  • Set transform-origin:bottom… center reads as EQ, bottom reads as city skyline; pick deliberately.
  • Infinite animations MUST respect prefers-reduced-motion — this one swaps to a static pattern.

Browser support

ChromeSafariFirefoxEdge
111+16.2+121+111+

Floor set by :has(), oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome 43+.

CSS keyframe animations on transform; universal support. The demo is fully usable with animations disabled.

Techniques used in this demo

Search CodeFronts

Loading…