Pure CSS Buttons & forms #form-hack #ui #controls

Rocker switch in pure CSS

A real checkbox underneath: click, tap or press Space. The rocker tips on :checked.

  • 62 lines of CSS
  • 5 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. A real <input type="checkbox"> holds the state, so keyboard, forms and screen readers all work.
  2. It is visually hidden (not display: none, which would remove it from the tab order).
  3. The rocker is the next sibling: input:checked + .rocker tips it from rotateX(-22deg) to rotateX(22deg).
  4. Wrapping everything in a <label> makes the whole switch clickable with zero JavaScript.
  5. Every length is a multiple of one base unit, --u, tied to the canvas — the rocker, its bezel rings, the gap and the LED — so the switch is the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.

Key techniques

  • input:checked + sibling
  • rotateX rocker
  • label makes the whole thing clickable

Copy-paste code

HTML 5 lines

<label class="switch">
  <input type="checkbox">
  <span class="rocker"><b>I</b><b>O</b></span>
  <em></em>
</label>

CSS 62 lines

.switch {
  /* one base unit: every length below is a multiple of it, so the switch is the same share of a
     card, the editor, a full screen and a recording canvas */
  --u: 0.34vmin;
  display: grid;
  justify-items: center;
  gap: calc(22 * var(--u));
  perspective: calc(320 * var(--u));
  cursor: pointer;
}

/* hidden but still focusable */
.switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.rocker {
  box-sizing: content-box; /* its size is its inside, its edge outside it, on any page */
  display: grid;
  grid-template-rows: 1fr 1fr;
  width: calc(90 * var(--u));
  height: calc(146 * var(--u));
  border-radius: calc(14 * var(--u));
  background: linear-gradient(#2b3050, #161a30);
  border: calc(1 * var(--u)) solid #4a5280;
  color: #8d95b3;
  font: 700 calc(25.6 * var(--u)) Inter, system-ui;
  text-align: center;
  box-shadow: 0 0 0 calc(9 * var(--u)) #05060c, 0 0 0 calc(10 * var(--u)) #4a5280;
  transform: rotateX(-22deg);                       /* OFF */
  transition: transform 0.18s cubic-bezier(0.3, 1.6, 0.5, 1);
}

.rocker b { display: grid; place-items: center; }

input:checked + .rocker {
  transform: rotateX(22deg);                        /* ON */
  color: #2ee6d6;
}

input:focus-visible + .rocker {
  outline: calc(2 * var(--u)) solid #2ee6d6;
  outline-offset: calc(14 * var(--u));
}

/* status LED */
.switch em {
  width: calc(14 * var(--u));
  height: calc(14 * var(--u));
  border-radius: 50%;
  background: #3a3f5c;
  transition: 0.2s;
}

input:checked ~ em {
  background: #2ee6d6;
  box-shadow: 0 0 calc(14 * var(--u)) #2ee6d6;
}
Standalone snippet · plain CSS, no build step, no dependencies.