Toggle
Description
Section titled “Description”.bp-toggle is a styled checkbox switch. It wraps a native <input type="checkbox" role="switch"> inside a <label>, so clicking anywhere on the component — track, thumb, or label text — toggles the state. All visual state is driven by CSS using the :checked and :disabled pseudo-classes plus the + sibling combinator. No JavaScript required.
Default
<label class="bp-toggle"><input class="bp-toggle__input" type="checkbox" role="switch" /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Enable notifications</span></label>Checked by default
<label class="bp-toggle"><input class="bp-toggle__input" type="checkbox" role="switch" checked /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Dark mode</span></label>Sizes
<label class="bp-toggle bp-toggle--sm"><input class="bp-toggle__input" type="checkbox" role="switch" /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Small</span></label>
<label class="bp-toggle"><input class="bp-toggle__input" type="checkbox" role="switch" /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Medium (default)</span></label>
<label class="bp-toggle bp-toggle--lg"><input class="bp-toggle__input" type="checkbox" role="switch" /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Large</span></label>Disabled
<label class="bp-toggle"><input class="bp-toggle__input" type="checkbox" role="switch" disabled /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Disabled (off)</span></label>
<label class="bp-toggle"><input class="bp-toggle__input" type="checkbox" role="switch" checked disabled /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Disabled (on)</span></label>No visible label (aria-label on input)
<label class="bp-toggle"><input class="bp-toggle__input" type="checkbox" role="switch" aria-label="Enable notifications"/><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span></label>Public API
Section titled “Public API”| Variable | Default | Description |
|---|---|---|
--toggle-track-bg | var(--bp-color-border) | Track background when unchecked |
--toggle-track-bg-checked | var(--bp-primary) | Track background when checked |
--toggle-track-radius | var(--bp-radius-full) | Track border radius (pill by default) |
--toggle-track-width | 2.75rem | Track width (md default) |
--toggle-track-height | 1.5rem | Track height (md default) |
--toggle-thumb-bg | #fff | Thumb fill color |
--toggle-thumb-size | 1.125rem | Thumb diameter (md default) |
--toggle-thumb-shadow | var(--bp-shadow-sm) | Thumb drop shadow |
--toggle-duration | var(--bp-duration-fast) | Transition duration for track color and thumb |
--toggle-label-color | var(--bp-color-text) | Label text color |
--toggle-label-gap | var(--bp-space-3) | Gap between track and label |
--toggle-disabled-opacity | 0.45 | Opacity of the full component when disabled |
Customization
Section titled “Customization”Brand colors via public API
<style>.demo-toggle--brand { --toggle-track-bg-checked: #7c3aed; --toggle-thumb-bg: #f5f3ff;}.demo-toggle--danger { --toggle-track-bg-checked: var(--bp-color-error);}</style><label class="bp-toggle demo-toggle--brand"><input class="bp-toggle__input" type="checkbox" role="switch" checked /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Custom brand color</span></label>
<label class="bp-toggle demo-toggle--danger"><input class="bp-toggle__input" type="checkbox" role="switch" checked /><span class="bp-toggle__track" aria-hidden="true"> <span class="bp-toggle__thumb"></span></span><span class="bp-toggle__label">Danger toggle</span></label> ✓ No axe violations tested 2026-05-12
Accessibility
Section titled “Accessibility”role="switch"on the<input>communicates the on/off semantics to screen readers. Assistive technology announces the state as “on” or “off” instead of “checked” or “unchecked”.- The
<label>wrapper means clicking or tapping anywhere — track, thumb, or label text — activates the control. No additional click handlers are needed. - When no visible label text is shown, add
aria-labeldirectly to the<input>so screen readers can identify the control. - The
__trackand__thumbelements carryaria-hidden="true"because they are purely decorative — the native<input>already exposes the semantic state. - Keyboard: Space or Enter toggles the switch. Focus lands on the
<input>, not on the track. - The focus ring appears on the visible
.bp-toggle__trackelement via the.bp-toggle__input:focus-visible + .bp-toggle__trackselector, giving a clear visual indicator without styling the hidden input. - Do not rely on color alone: the thumb position (left = off, right = on) provides a second, color-independent signal.
- WCAG 2.1 AA: ensure the checked track color (
--toggle-track-bg-checked) has at least 3:1 contrast against the page background under WCAG 1.4.11 (non-text contrast). The default--bp-primaryis chosen to meet this threshold — verify if overriding it.
Browser APIs
Section titled “Browser APIs”| API | Availability | Used for | Without it | Polyfill |
|---|---|---|---|---|
:has() | Widely available Baseline 2023 | cursor: not-allowed on root when input is :disabled | Cursor stays pointer; the track still loses opacity via the + combinator rule | None needed — visual degradation only |
prefers-reduced-motion | Widely available Baseline 2020 | Disables thumb slide and track fade transitions | Transitions still play (no functional impact) | None needed |
Internals
Section titled “Internals”--_track-bg,--_track-bg-checked,--_track-radius,--_track-width,--_track-height,--_thumb-bg,--_thumb-size,--_thumb-shadow,--_duration,--_label-color,--_label-gap,--_disabled-opacity— component-private resolved values, do not set directly.- The input is visually hidden with
opacity: 0; position: absolute; width: 0; height: 0rather thandisplay: noneorvisibility: hidden— it must remain in the tab order and be interactable. - Thumb translation uses
calc(var(--_track-width) - var(--_thumb-size) - (var(--_track-height) - var(--_thumb-size)))which simplifies totrack-width - track-height. The inner term(track-height - thumb-size) / 2is the symmetric padding on each side; subtracting it twice gives the travel distance. - Size variants (
--sm,--lg) override only the three sizing tokens — all proportions self-adjust via the same calc. - The
transition: nonerule inside@media (prefers-reduced-motion: reduce)targets both.bp-toggle__trackand.bp-toggle__thumbto suppress both the color fade and the slide animation.