Selection controls
The controls that record a choice rather than take text: checkboxes, radios, and the toggle switch. Every one of them is a native input underneath, so the keyboard, the focus ring and :disabled are the browser's, not the kit's.
Checkboxes and radios
_78-check goes on the <label> and handles the alignment and the
accent tint. It works for both type="checkbox" and
type="radio" — wrapping the input in the label means the text is the control's
click target and its accessible name at the same time.
<div class="site-demo-block">
<div class="_78-flex _78-flex-wrap _78-gap-16">
<label class="_78-check"><input type="checkbox" checked> Weekly digest</label>
<label class="_78-check"><input type="checkbox"> Share usage data</label>
<label class="_78-check"><input type="checkbox" disabled> Unavailable</label>
</div>
<div class="_78-flex _78-flex-wrap _78-gap-16">
<label class="_78-check"><input type="radio" name="cadence" checked> Daily</label>
<label class="_78-check"><input type="radio" name="cadence"> Weekly</label>
<label class="_78-check"><input type="radio" name="cadence"> Never</label>
</div>
</div>
Radios need a shared name to form a group. Give the group itself a label with
role="group" and aria-labelledby, as shown under
Labels outside a field on the Forms
page — _78-label is what labels a group of these.
Toggle switch
_78-switch is a real <input type="checkbox"> restyled as a track and
a sliding thumb — not a <div> pretending to be one. Tab and
Space, the focus ring, :disabled, label association and the checked state
are all the native control's, so a switch posts in a form and reads correctly to a screen reader
with nothing to re-implement.
Two ways to write one
Wrap the input in a _78-switch label and the text comes along; or put
_78-switch-input on a standalone input where the label is elsewhere — a table
cell, a toolbar. Inside the wrapper the class on the input is optional; standing alone it is
required, because it is what carries the styling.
<div class="_78-flex-col _78-gap-12">
<label class="_78-switch">
<input type="checkbox" role="switch" checked>
<span class="_78-switch-label">Email me when an order fills</span>
</label>
<label class="_78-switch _78-switch-sm">
<input type="checkbox" role="switch">
<span class="_78-switch-label">Compact rows — the _78-switch-sm size</span>
</label>
<label class="_78-switch">
<input type="checkbox" role="switch" checked disabled>
<span class="_78-switch-label">Two-factor (enforced by your plan)</span>
</label>
<div class="_78-flex _78-gap-8">
<input class="_78-switch-input" type="checkbox" role="switch" aria-label="Live updates">
<span class="_78-text-dim">Standalone _78-switch-input, named by aria-label</span>
</div>
</div>
Settings rows
_78-switch-row is the settings-page shape: full width, label on the left, switch pushed
to the right edge, with an optional dim second line in _78-switch-hint. The whole row is
still one <label>, so all of it is the click target.
Stacked rows space themselves, --space-4 apart, the same way stacked cards do. No wrapper
needed.
<div class="_78-card">
<div class="_78-card-title">Notifications</div>
<label class="_78-switch _78-switch-row">
<span class="_78-switch-label">Confirm before closing an account
<span class="_78-switch-hint">Shows a modal every time.</span>
</span>
<input type="checkbox" role="switch" checked>
</label>
<label class="_78-switch _78-switch-row">
<span class="_78-switch-label">Weekly summary email
<span class="_78-switch-hint">Sent on Mondays, 8am in your time zone.</span>
</span>
<input type="checkbox" role="switch">
</label>
<label class="_78-switch _78-switch-row">
<span class="_78-switch-label">Product updates
<span class="_78-switch-hint">A few times a year, never more than once a month.</span>
</span>
<input type="checkbox" role="switch" checked>
</label>
</div>
_78-flex-col _78-gap-16 wrapper to space them. That wrapper still
works and looks the same (the rows drop their own margin inside a _78-flex-col, so the
spacing doesn't double), so there is nothing to change. A different spacing is still just a
_78-flex-col with the gap you want. A wrapper of your own that spaces rows with
gap will now show the gap plus 16px; drop one of the two.
| Class | What it is |
|---|---|
_78-switch | The wrapping <label>: inline flex, a --space-2 gap, pointer cursor. |
_78-switch-input | The checkbox, styled as the track and thumb. Optional inside the wrapper (a bare input[type="checkbox"] child is styled too), required when the switch stands alone. |
_78-switch-label | The text beside it. |
_78-switch-hint | A second, dim line under the label. |
_78-switch-sm | Small: a 28×16 track instead of 36×20. Goes on the wrapper, or on a standalone input. |
_78-switch-row | Settings row: full width, label left, switch pushed right. |
Color and size
Off is a --bg3 track with a --border edge and a --dim thumb;
on is an --accent track with an --accent-text thumb. Hover raises the edge
to --dim, focus is the kit's 2px accent outline, and :disabled dims both
the control and its label. Nothing is hardcoded, so the switch re-themes with everything else.
Size is three local custom properties on the input, which makes a one-off size an inline override rather than new CSS:
<label class="_78-switch">
<input type="checkbox" role="switch" checked
style="--switch-w: 44px; --switch-h: 24px; --switch-pad: 3px">
<span class="_78-switch-label">A bigger switch, no new CSS</span>
</label>
role="switch" when it takes effect immediately_78-check checkbox instead when the change only applies once the
form is submitted — that is a choice being made, not a setting being flipped.