SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/toggles-switches

8 CSS Toggle Switches

8 Pure CSSPublished 2026-10-04Free · no sign-up

Eight toggle switches built on real checkbox and radio inputs, so they work with the keyboard, submit with forms and are announced as switches by screen readers. Each one is pure CSS apart from the label text, and every design shows a clear focus ring.

01. Classic Pill Switch

FormAccessible

The familiar rounded switch, built from a real checkbox with role="switch" so screen readers announce it as on or off. The thumb slides with a transform and the track changes colour, and a visible focus ring appears for keyboard users only.

View code
<label class="sw">
  <input type="checkbox" role="switch" checked>
  <span>Email notifications</span>
</label>
.sw { display: inline-flex; align-items: center; gap: 12px; color: #e7ebf5; font: 600 15px/1.2 Inter, system-ui, sans-serif; cursor: pointer; }
.sw input { appearance: none; position: relative; width: 52px; height: 30px; margin: 0; border-radius: 999px; background: #2a3142; cursor: pointer; transition: background .2s; }
.sw input::before { content: ""; position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%; background: #fff; box-shadow: 0 2px 6px rgba(0,0,0,.35); transition: transform .2s; }
.sw input:checked { background: #5b8cff; }
.sw input:checked::before { transform: translateX(22px); }
.sw input:focus-visible { outline: 2px solid #9db8ff; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .sw input, .sw input::before { transition: none; } }

02. Switch with On / Off Text

FormLabelled

A wider track that spells out its state, so the meaning never depends on colour alone. The ON and OFF words are drawn with pseudo-elements and swap sides as the thumb moves, which helps people with colour vision deficiency read the state at a glance.

View code
<label class="sw2">
  <input type="checkbox" role="switch">
  <span class="sw2-text">Auto-renew subscription</span>
</label>
.sw2 { display: inline-flex; align-items: center; gap: 14px; color: #1d2433; font: 600 15px Inter, system-ui, sans-serif; cursor: pointer; }
.sw2 input { appearance: none; position: relative; width: 74px; height: 32px; margin: 0; border-radius: 999px; background: #d5dbe7; cursor: pointer; transition: background .2s; }
.sw2 input::before { content: ""; position: absolute; top: 4px; left: 4px; width: 24px; height: 24px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(16,24,40,.3); transition: transform .2s; }
.sw2 input::after { content: "OFF"; position: absolute; right: 11px; top: 50%; transform: translateY(-50%); color: #4b5568; font: 800 11px Inter, sans-serif; letter-spacing: .06em; }
.sw2 input:checked { background: #16a34a; }
.sw2 input:checked::before { transform: translateX(42px); }
.sw2 input:checked::after { content: "ON"; right: auto; left: 12px; color: #fff; }
.sw2 input:focus-visible { outline: 2px solid #2563eb; outline-offset: 3px; }

03. Sun and Moon Theme Toggle

Dark modeIcon

A dark-mode switch whose thumb carries the icon: a sun when light is selected and a moon, cut out with a second circle, when dark is selected. The page label stays hidden visually but is read aloud, and the icons are pure CSS gradients with no images.

View code
<label class="theme">
  <input type="checkbox" role="switch" checked>
  <span class="theme-sr">Dark mode</span>
</label>
.theme { display: inline-block; cursor: pointer; }
.theme-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.theme input { appearance: none; position: relative; width: 84px; height: 42px; margin: 0; border-radius: 999px; background: linear-gradient(180deg, #8ec5ff, #c8e4ff); box-shadow: inset 0 2px 6px rgba(0,0,0,.2); cursor: pointer; transition: background .3s; }
.theme input::before { content: ""; position: absolute; top: 5px; left: 5px; width: 32px; height: 32px; border-radius: 50%; background: radial-gradient(circle, #ffd84d 55%, #ffb72b 100%); box-shadow: 0 0 0 4px rgba(255,216,77,.35); transition: transform .3s, background .3s, box-shadow .3s; }
.theme input:checked { background: linear-gradient(180deg, #141a33, #283058); }
.theme input:checked::before { transform: translateX(42px); background: radial-gradient(circle at 70% 30%, transparent 9px, #e8ecf7 9.5px); box-shadow: none; }
.theme input:focus-visible { outline: 2px solid #ffd84d; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .theme input, .theme input::before { transition: none; } }

04. Square Hardware Switch

FormSharp

A squared-off switch with a ridged knob, inspired by physical rocker switches on studio equipment. Corners stay sharp, the knob gets three grip lines from a repeating gradient, and the checked state uses a strong dark fill instead of a bright colour.

View code
<label class="hw">
  <input type="checkbox" role="switch" checked>
  <span>Monitor input</span>
</label>
.hw { display: inline-flex; align-items: center; gap: 12px; color: #111827; font: 700 14px "JetBrains Mono", ui-monospace, monospace; text-transform: uppercase; letter-spacing: .06em; cursor: pointer; }
.hw input { appearance: none; position: relative; width: 60px; height: 30px; margin: 0; border: 2px solid #111827; border-radius: 4px; background: #fff; cursor: pointer; transition: background .15s; }
.hw input::before { content: ""; position: absolute; top: 2px; left: 2px; width: 24px; height: 22px; border-radius: 2px; background: repeating-linear-gradient(90deg, #9ca3af 0 2px, transparent 2px 5px) center / 11px 10px no-repeat, #e5e7eb; box-shadow: inset 0 -2px 0 #9ca3af; transition: transform .15s; }
.hw input:checked { background: #111827; }
.hw input:checked::before { transform: translateX(28px); background-color: #facc15; }
.hw input:focus-visible { outline: 3px solid #f59e0b; outline-offset: 3px; }

05. Neon Glow Switch

GlowDark

A switch for dark interfaces where the active state glows. Layered box-shadows create the halo around both the track and the thumb, and the colour moves from a dim violet outline to a bright cyan when it is turned on.

View code
<label class="neon">
  <input type="checkbox" role="switch" checked>
  <span>Boost mode</span>
</label>
.neon { display: inline-flex; align-items: center; gap: 14px; color: #c9d4ff; font: 700 15px Inter, system-ui, sans-serif; cursor: pointer; }
.neon input { appearance: none; position: relative; width: 64px; height: 32px; margin: 0; border: 2px solid #6d4bd8; border-radius: 999px; background: #0d0f1c; cursor: pointer; transition: border-color .25s, box-shadow .25s; }
.neon input::before { content: ""; position: absolute; top: 4px; left: 4px; width: 20px; height: 20px; border-radius: 50%; background: #6d4bd8; transition: transform .25s, background .25s, box-shadow .25s; }
.neon input:checked { border-color: #22d3ee; box-shadow: 0 0 12px rgba(34,211,238,.55), inset 0 0 10px rgba(34,211,238,.25); }
.neon input:checked::before { transform: translateX(32px); background: #22d3ee; box-shadow: 0 0 10px #22d3ee, 0 0 22px rgba(34,211,238,.7); }
.neon input:focus-visible { outline: 2px solid #fff; outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .neon input, .neon input::before { transition: none; } }

06. Settings List with Switches

FormLayout

A realistic settings panel: each row has a title, a one-line explanation and a switch on the right. The description is tied to its switch with aria-describedby, so assistive technology reads the explanation after the label instead of losing it.

View code
<section class="set">
  <h2>Notifications</h2>
  <div class="row">
    <div><p class="t" id="s1">Order updates</p><p class="d" id="s1d">When an order ships or is delivered.</p></div>
    <input type="checkbox" role="switch" aria-labelledby="s1" aria-describedby="s1d" checked>
  </div>
  <div class="row">
    <div><p class="t" id="s2">Weekly report</p><p class="d" id="s2d">A summary of sales every Monday morning.</p></div>
    <input type="checkbox" role="switch" aria-labelledby="s2" aria-describedby="s2d">
  </div>
  <div class="row">
    <div><p class="t" id="s3">Low stock alerts</p><p class="d" id="s3d">When any product falls below 5 units.</p></div>
    <input type="checkbox" role="switch" aria-labelledby="s3" aria-describedby="s3d" checked>
  </div>
</section>
.set { box-sizing: border-box; max-width: 100%; width: min(420px, 92vw); padding: 8px 20px; border-radius: 16px; background: #fff; box-shadow: 0 10px 30px -12px rgba(16,24,40,.25); font-family: Inter, system-ui, sans-serif; }
.set h2 { margin: 14px 0 4px; color: #101828; font-size: 16px; }
.row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 0; border-top: 1px solid #eef0f4; }
.row:first-of-type { border-top: 0; }
.t { margin: 0; color: #101828; font-weight: 600; font-size: 14px; }
.d { margin: 3px 0 0; color: #5d6679; font-size: 13px; }
.row input { appearance: none; flex: none; position: relative; width: 44px; height: 24px; margin: 0; border-radius: 999px; background: #d0d5dd; cursor: pointer; transition: background .2s; }
.row input::before { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(16,24,40,.25); transition: transform .2s; }
.row input:checked { background: #7c3aed; }
.row input:checked::before { transform: translateX(20px); }
.row input:focus-visible { outline: 2px solid #7c3aed; outline-offset: 3px; }

07. Switch Size Scale

FormSizes

One switch in three sizes driven by a single custom property. Change --s on the element and the track, thumb and travel distance all scale together, which keeps a design system consistent without writing three separate rule sets.

View code
<div class="scale">
  <label class="ss" style="--s:16px"><input type="checkbox" role="switch" checked><span>Small</span></label>
  <label class="ss" style="--s:22px"><input type="checkbox" role="switch" checked><span>Medium</span></label>
  <label class="ss" style="--s:30px"><input type="checkbox" role="switch"><span>Large</span></label>
</div>
.scale { display: grid; gap: 18px; font: 600 14px Inter, system-ui, sans-serif; color: #dbe2f2; }
.ss { display: inline-flex; align-items: center; gap: 12px; cursor: pointer; }
.ss input { appearance: none; position: relative; width: calc(var(--s) * 2 + 4px); height: calc(var(--s) + 4px); margin: 0; border-radius: 999px; background: #323a4f; cursor: pointer; transition: background .2s; }
.ss input::before { content: ""; position: absolute; top: 2px; left: 2px; width: var(--s); height: var(--s); border-radius: 50%; background: #fff; transition: transform .2s; }
.ss input:checked { background: #f97316; }
.ss input:checked::before { transform: translateX(var(--s)); }
.ss input:focus-visible { outline: 2px solid #fdba74; outline-offset: 3px; }

08. Monthly / Yearly Toggle

PricingRadio

The two-option toggle used above pricing tables. It is built from two radio buttons inside a fieldset, so arrow keys move between Monthly and Yearly, and a sliding highlight is positioned with the :has() selector so no JavaScript is needed.

View code
<fieldset class="bill">
  <legend class="bill-sr">Billing period</legend>
  <input type="radio" name="bill" id="b-m" checked><label for="b-m">Monthly</label>
  <input type="radio" name="bill" id="b-y"><label for="b-y">Yearly <em>-20%</em></label>
  <span class="bill-pill" aria-hidden="true"></span>
</fieldset>
.bill { position: relative; display: inline-grid; grid-template-columns: 1fr 1fr; margin: 0; padding: 4px; border: 0; border-radius: 999px; background: #e7eaf1; font: 700 14px Inter, system-ui, sans-serif; }
.bill-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.bill input { position: absolute; opacity: 0; }
.bill label { position: relative; z-index: 1; padding: 10px 20px; border-radius: 999px; color: #475467; text-align: center; cursor: pointer; transition: color .2s; }
.bill label em { color: #15803d; font-style: normal; font-size: 12px; }
.bill input:checked + label { color: #101828; }
.bill input:focus-visible + label { outline: 2px solid #2563eb; outline-offset: 2px; }
.bill-pill { position: absolute; top: 4px; bottom: 4px; left: 4px; width: calc(50% - 4px); border-radius: 999px; background: #fff; box-shadow: 0 2px 6px rgba(16,24,40,.15); transition: transform .25s; }
.bill:has(#b-y:checked) .bill-pill { transform: translateX(100%); }
@media (prefers-reduced-motion: reduce) { .bill-pill { transition: none; } }

About these CSS Toggle Switches

Why start from a checkbox

A switch is a checkbox with a different look. Using <input type="checkbox" role="switch"> gives you keyboard support with Space, a checked state the browser remembers, and form submission, with no JavaScript. Styling it with appearance: none keeps all of that while letting you draw the track and thumb yourself.

Drawing the track and thumb

The input itself becomes the track, and a ::before pseudo-element becomes the thumb. On :checked the track changes colour and the thumb moves with transform: translateX(), which animates smoothly because transforms do not trigger layout. Keep the travel distance equal to the track width minus the thumb width and both paddings.

Do not rely on colour

Green for on and grey for off is not enough for people with colour vision deficiency. The On / Off and icon designs here add text or a symbol, and the position of the thumb is a second cue. If a switch takes effect immediately, say so in the label; if it needs a Save button, a checkbox is often clearer.

Switch or checkbox?

Use a switch when the change happens straight away, such as turning notifications on. Use a checkbox inside a form that is submitted later, and radio buttons when there are two named options like Monthly and Yearly, as in the billing toggle demo.

Frequently asked questions

Do these switches need JavaScript?

No. Every switch here works with CSS alone; the billing toggle uses the :has() selector to move its highlight.

Are they accessible?

They use real inputs with role="switch" or radio buttons, visible labels and :focus-visible outlines, so keyboard and screen reader users can operate them.

Can I change the size?

Yes. The size scale demo drives the track, thumb and travel from one custom property.