78 UI Kit
Components

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.

Text inputs, selects, textareas and validation live on Forms. The one-of-N segmented control is on Tabs — it switches a view rather than filling in a field.

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.

checkboxes and radios
<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.

Standalone _78-switch-input, named by aria-label
_78-switch
<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.

Notifications
_78-switch-row
<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>
Changed in 0.3.0
Rows used to need a _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.
ClassWhat it is
_78-switchThe wrapping <label>: inline flex, a --space-2 gap, pointer cursor.
_78-switch-inputThe 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-labelThe text beside it.
_78-switch-hintA second, dim line under the label.
_78-switch-smSmall: a 28×16 track instead of 36×20. Goes on the wrapper, or on a standalone input.
_78-switch-rowSettings 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:

switch sizing
<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>
Use role="switch" when it takes effect immediately
It changes nothing visually; it makes a screen reader say “on” / “off” rather than “checked” / “not checked”. Reach for a plain _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.