Toggle

stable
Since 1.1
On/off switch with three sizes, a slim treatment, label and description composition, and read-only and invalid states.
Forms
primitive
Form Control
Needs State Matrix
import { Toggle } from '@ivim/native/components';

When to use

Use when
A setting takes effect immediately, with no separate save step.
The choice is binary and reads naturally as on or off.
Don't use when
The choice is part of a form that is submitted later — use a Checkbox, which reads as "will be applied".
There are more than two states — use ButtonGroup or RadioGroup.
The user is agreeing to terms — a checkbox is the established consent control.

Playground

Adjust the controls to preview live prop combinations. The code snippet updates to match the current selection.
Weekly summary
size
size
Label placement
labelPlacement
Slim
slim
Invalid
isInvalid
Read only
isReadOnly
Disabled
isDisabled
Label
label
<Toggle label="Weekly summary" />

Example

States
Off
On
With label and description
Weekly summary
Send a digest every Monday.
Label before the control
Compact mode
Invalid
Accept data processing
Required to continue.
Read only (still announced as enabled)
Two-factor authentication
Set by your administrator.
Disabled on
Disabled off
Sizes and treatments
Sizes (sm, md, lg)
Slim treatment
Migration note
`Switch` has been removed. It was a thin wrapper over React Native’s own switch with `value`/`onValueChange` and no accessible name, which left the family with two public controls and two vocabularies. `Toggle` is now the single control: replace `value` with `isSelected` and `onValueChange` with `onChange`.

Anatomy

The parts that compose this component.
1
Track
The rail, animating between the off and on colors.
2
Thumb
The knob, translating across the track.
3
Label
The setting name, and the accessible name. Can sit before or after the control.
4
Description
Supporting text beneath the label; takes the error color when invalid.

Guidelines

Do
Label the thing being switched, not the action ("Weekly summary", not "Enable weekly summary").
Do
Apply the change immediately; if it can fail, show the failure rather than silently reverting.
Do
Use isReadOnly for a setting fixed by policy, so it stays announced as an enabled, explicable control.
Don't
Pair a toggle with a Save button — that combination implies the change is pending, which contradicts the control.
Don't
Use a toggle when the two states are not opposites (for example "Card" versus "Bank transfer").

Props

Public props for this component. Required props are marked; defaults reflect the component implementation.
isSelected / defaultSelected
boolean
Controlled and uncontrolled on state.
onChange
(isSelected: boolean) => void
Fires with the next on state.
label / description
ReactNode
Setting name and supporting text.
labelPlacement
'start' | 'end'
default: 'end'
Whether the label sits before or after the control.
size
'sm' | 'md' | 'lg'
default: 'md'
Track, thumb, and label scale.
slim
boolean
default: false
Thin rail with an overhanging thumb, for dense settings lists. Presentational only.
isReadOnly
boolean
default: false
Blocks input but still announces as an enabled control.
isDisabled
boolean
default: false
Dims, blocks input, and announces disabled.
isInvalid
boolean
default: false
Applies the error treatment to the off track and description.
aria-label
string
Accessible name when there is no visible label, or when the label is not plain text.

Deviations from Untitled UI

Where this component deliberately differs from Untitled UI's API, and why. These are kept choices, not gaps to close.
Untitled UI
`hint?: ReactNode` doubling as error text when invalid
Ours
`description` + `errorMessage`
UUI's `hint` is a UUI-ism layered over React Aria, which itself uses `description`/`errorMessage`. We follow the substrate; both slots can render simultaneously.

Accessibility

Roles and screen-reader behavior this component provides, plus keyboard interactions.
Announces accessibilityRole="switch" with checked state, updated immediately on press.
Takes its name from a string label, or from aria-label when there is no visible label.
Read-only rejects input but still announces as an enabled control, so a policy-fixed setting is explicable rather than merely unavailable.
Disabled announces as disabled and blocks input.
The slim treatment is purely presentational — semantics and behavior are unchanged.
Tab
Moves focus to the toggle.
Space / Enter
Flips the toggle.

Platform & RTL

Behavior that differs across the platforms this component runs on.
Web
Focusable with a visible inset focus ring matched to the pill radius.
iOS
VoiceOver announces the switch trait and its state; the thumb animates with the shared motion tokens.
Android
TalkBack announces the switch trait and its state.
RTL
Track travel and label placement mirror with the writing direction.

State coverage

Interaction and visual states this component supports.
default
focus
error
disabled
selected

Token references

Semantic design tokens this component page exercises. Tap one to edit it live.