Switch

A track and thumb that turns one setting on or off. Use it for settings that take effect at once, such as notifications or model access.

Sizes and states

default is a 32 × 18px track for settings rows. sm is a 24 × 14px track for dense control strips.

Off

default
sm

On

default
sm

Disabled

default
sm

Disabled on

default
sm

Invalid

default
sm

Controlled with a label

Wrap the switch in a Label and pass a state setter to onCheckedChange. Clicking the text toggles it.

Bud is idling.

In settings rows

Give the switch an id and pass the same id to SettingsRow htmlFor, so the row label names and toggles it.

Local preferences

This toggle never sends email.
Pause fictional notifications.

Project name

Meadow

Model access

Each row names its switch with aria-label. While a save runs, the row says Saving… and its switch is aria-busy. Every switch is disabled while a save, reload, or conflict is unresolved.

  • Claude Sonnet 4.5
    Credits per 1M input / output tokens: 3 / 15
  • GPT-5
    Credits per 1M input / output tokens: 1.25 / 10
  • DeepSeek V3.2
    Credits per 1M input / output tokens: 0.28 / 0.42
  • Kimi K2 Thinking with extended reasoning
    Credits per 1M input / output tokens: 0.6 / 2.5
  • Grok 4
    Credits per 1M input / output tokens: Unknown / Unknown

Usage

Switch usageTSX
import { Switch } from "@tulipfarm/sprout/switch";
import { Label } from "@tulipfarm/sprout/label";
import { SettingsRow } from "@tulipfarm/sprout/settings-list";

const [paused, setPaused] = useState(false);

<Label>
  <Switch checked={paused} onCheckedChange={setPaused} />
  Pause motion
</Label>

<SettingsRow label="Email summaries" htmlFor="summary">
  <Switch id="summary" defaultChecked />
</SettingsRow>

<Switch
  aria-label={`Allow ${model.name}`}
  checked={model.enabled}
  disabled={saving}
  aria-busy={pending}
  onCheckedChange={(enabled) => save(model.id, enabled)}
/>

Pass checked and onCheckedChange to control the switch, or defaultChecked to let it keep its own state.onCheckedChange gets a boolean, so a state setter can be passed straight in.

Props

size is "default" (a 32 × 18px track with a 16px thumb) or "sm" (a 24 × 14px track with a 12px thumb). Both have a hit area 12px wider and 8px taller on each side.

disabled dims the switch and blocks changes; a disabled<fieldset> around it does the same. Setaria-invalid to draw a danger border and ring. Setaria-busy while a change is saving to show a progress cursor.

The switch also takes Base UI props such as id,name, value, required,readOnly, inputRef, and state functions forclassName.

Accessibility

Switch renders an element with role="switch" andaria-checked, plus a hidden input for forms. Tab moves focus to it, and Space or Enter toggles it. A focused switch shows a 2px focus ring. Name every switch with a wrapping Label, aLabel htmlFor or SettingsRow htmlFor, oraria-label.

The track color changes in 120ms and the thumb slides in 180ms. Both stop when reduced motion is requested. The thumb slides the other way in right-to-left text. In forced colors mode the track gets a system border and an on switch is highlighted.

Theme tokens

Off uses a --sprout-muted track with a--sprout-surface thumb (--sprout-text in dark mode). On uses --sprout-primary with a--sprout-on-primary thumb, so the opt-in TulipFarm theme tints it green. Invalid uses --sprout-danger, and focus uses--sprout-focus.