Terracotta
GitHub

Use Switch for a setting that takes effect at once, such as aeroplane mode or notifications. Screen readers announce it as "on" or "off".

  • For a value submitted with a form, use Checkbox.
  • For a button that stays pressed, such as bold in an editor, use Toggle.
import { CheckStateChild, useCheckState } from 'terracotta/states';
import { Switch } from 'terracotta/switch';

Anatomy

<Switch/> {/* role="switch" aria-checked, owns the checked state */}

Examples

Uncontrolled

The switch owns its state. onChange only tells you what happened. The content of the switch is its label.

The switch carries tc-checked while it is on, and aria-checked alongside it. The knob slides because the theme styles [tc-checked].

Controlled

Nothing changes on screen until setEnabled runs. In controlled mode the component only reports the state it wants to move to.

Labelled from outside

When the visible label is not inside the switch, point aria-labelledby at it:

<span id="wifi-label">Wi-Fi</span>
<Switch class="switch" defaultChecked={false} aria-labelledby="wifi-label">
  <span class="switch-track" aria-hidden="true">
    <span class="switch-knob" />
  </span>
</Switch>

Render prop

<Switch class="switch" defaultChecked={false}>
  {state => <span>{state.checked() ? 'Dark mode on' : 'Dark mode off'}</span>}
</Switch>

Keep the label the same in both states when you can. The switch already announces whether it is on, so a label that changes says it twice.

State attributes

Element Attribute Present when
Switch tc-switch, tc-button Always
Switch tc-checked The switch is on
Switch tc-disabled The switch is disabled

aria-checked is always present too, as true or false. A switch has no mixed state.

Styling

[tc-switch] { /* base */ }
[tc-switch][tc-checked] .switch-knob { translate: 0.95rem 0; }
[tc-switch][tc-disabled] { opacity: 0.5; }
[tc-switch]:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }

Reading the state in code

Member Type Description
checked() boolean | undefined Whether the switch is on.
setState(value) (boolean) => void Sets the state.
check() / uncheck() () => void Sets true / false.
toggle() () => void Flips the state.
disabled() boolean Whether the switch is disabled.

You can reach this state three ways: as the render-prop argument on Switch, through <CheckStateChild>, or with useCheckState() inside any descendant component. Full reference in check state.

Keyboard

Key Action
Space Flips the switch
Enter Flips the switch. The pattern makes this optional.
Tab Focuses the switch, unless it is disabled

API

<Switch>

Renders a <button> by default.

Prop Type Default Description
as ValidConstructor 'button' Element or component to render as. Non-<button> elements get Enter/Space activation from Button.
defaultChecked boolean none Initial state, uncontrolled. Mutually exclusive with checked.
checked boolean none Current state, controlled. Mutually exclusive with defaultChecked.
disabled boolean false Blocks changes and marks the switch disabled.
onChange (checked: boolean) => void none Called with the new state on every change.
ref DynamicNode<T> | (el) => void none Handle to the rendered element.
children JSX.Element | (state: CheckStateProperties) => JSX.Element none The label, or a render prop receiving the check state.
…rest props of as none Forwarded to the rendered element.

Rendered attributes

Attribute Value
role "switch"
tabindex 0, or -1 when disabled
aria-checked true / false
aria-disabled, disabled Mirrors the disabled state
tc-switch, tc-button ""
tc-checked "" when on
tc-disabled "" when disabled