CSS Switch Generator
Design a toggle switch visually, then copy the HTML + CSS.
About This Tool
The switch is a hidden checkbox paired with a styled track and thumb built from <span> elements. Sliding the thumb is handled entirely by a CSS transition on :checked, so no script is needed just to animate it.
Why the checkbox is hidden with opacity, not display: none
The generated markup sets the real <input type="checkbox"> to opacity: 0; width: 0; height: 0; rather than the seemingly simpler display: none. That distinction matters for accessibility: display: none removes an element from both the visual layout and the accessibility tree, making it untabbable and invisible to screen readers, effectively deleting it from the interaction model entirely. Setting opacity to zero and collapsing its dimensions instead keeps the checkbox fully present, focusable, and operable via keyboard Space or Enter, it's just rendered invisibly, with the styled track and thumb elements providing the visual representation on top of it. Clicking or tabbing to the switch still toggles a real, semantic checkbox underneath.
How the sibling selectors connect the checkbox state to the visuals
Notice the specific order in the generated markup: the checkbox input comes first, immediately followed by the track span, which itself contains the thumb span. That order isn't arbitrary, it's required for the CSS to work at all. The rule input:checked + .track uses the adjacent sibling combinator (+), which only matches an element that comes directly after the specified one in the same parent, so the track can only react to the checkbox's checked state because it sits immediately next to it in the DOM. The thumb's position is then controlled by input:checked + .track .thumb, a sibling selector followed by a descendant selector, reaching from the checkbox, to the adjacent track, down into the track's own thumb child.
The math that keeps the thumb from ever overflowing the track
Three numbers work together here, and none of them are arbitrary. The track height is calculated as roughly 56% of the width you choose, which keeps the pill shape's proportions consistent across every size rather than looking stretched at large widths or cramped at small ones. The thumb's diameter is set to the track height minus 6px, leaving exactly 3px of breathing room on the top and bottom when it's positioned at top: 3px; left: 3px. And the thumb's slide distance when checked, translateX(width − height), is precisely the horizontal space left over once the thumb's own diameter (which equals the track height) is subtracted from the track's full width, this is what guarantees the thumb always lands flush against the right edge with the same 3px margin it started with on the left, at any width setting.
A real gap: no visible keyboard focus style included
Because the real checkbox is visually hidden, it also loses its browser-default focus outline when a keyboard user tabs to it, and this generator's output doesn't add a replacement focus indicator anywhere in the visible track or thumb. For a production interface, this is worth fixing by adding a rule like .switch input:focus-visible + .track { box-shadow: 0 0 0 2px your-color; } to the copied CSS, so keyboard users still get a clear visual signal of which switch currently has focus, mirroring what the native (now-hidden) checkbox would have shown automatically.
Only the track's background transitions, not the thumb's color
Look closely at the two transition rules: the track has transition: background 0.2s ease, and the thumb has transition: transform 0.2s ease, each animating a different property. The track smoothly fades between your Off Color and On Color, while the thumb, which always stays white regardless of state, simply slides horizontally rather than changing color itself. Animating transform instead of a layout property like left or margin also keeps the thumb's motion smooth and GPU-friendly, the same performance reasoning that applies to any CSS animation built around transform rather than raw position properties.
Frequently Asked Questions
Why is the checkbox hidden with opacity: 0 instead of display: none?
display: none would remove the checkbox from both the visual layout and the accessibility tree, making it untabbable and invisible to screen readers. Using opacity: 0 with collapsed dimensions keeps it focusable and operable by keyboard while the styled track and thumb provide the visible appearance.
Why does the order of the input and span elements in the markup matter?
The CSS relies on the adjacent sibling combinator (input:checked + .track), which only matches an element directly following the specified one. If the track span isn't placed immediately after the checkbox input in the markup, the checked-state styling won't apply at all.
How does the tool make sure the thumb never overflows the track at different widths?
The track height, thumb size, and slide distance are all calculated from the same width value using fixed relationships (height is about 56% of width, thumb is height minus 6px, and slide distance is width minus height), so the thumb always ends up flush against the track's edges with consistent margins regardless of the chosen width.
Does this switch include a visible focus indicator for keyboard users?
Not by default. Since the real checkbox is visually hidden, its native focus outline disappears too, and no replacement is included in the generated CSS. Adding a :focus-visible rule targeting the track is recommended for production use so keyboard users can see which switch is focused.
Is this switch still a real, accessible checkbox under the hood?
Yes, it's a genuine input type="checkbox", just visually restyled. It responds to keyboard toggling, screen reader announcements, and form submission exactly like a native checkbox, since only its appearance is being overridden, not its underlying behavior.
Why is the track's border-radius set to the full height value instead of 50%?
Setting border-radius equal to the track's own height guarantees a fully rounded pill shape at any width, since CSS automatically clamps a radius that exceeds half the shorter dimension. This produces the same fully-rounded look as 50% would on a fixed shape, while working correctly across every width the slider allows.