Switch
A toggle control for on/off states that take effect immediately. Supports labels, descriptions, loading states, and validation. Use it for settings or preferences that apply instantly. For changes requiring a form submission, use a checkbox instead.
Installation
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
Requires React 19+, base CSS and a Theme provider. See Setup.
Switch
A toggle control for on/off states that take effect immediately. Supports labels, descriptions, loading states, and validation. Use it for settings or preferences that apply instantly. For changes requiring a form submission, use a checkbox instead.
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | React.Ref<HTMLInputElement> | Ref forwarded to the underlying <input> element. | |
| label * | string | Label text for the switch (always rendered for accessibility). | |
| value * | boolean | Whether the switch is on or off. | |
| onChange | (checked: boolean, e: ChangeEvent<HTMLInputElement>) => void | Callback fired when the switch state changes. | |
| changeAction | (checked: boolean, e: ChangeEvent<HTMLInputElement>) => void | Promise<void> | Async action fired after onChange. Triggers optimistic UI and shows a loading spinner until the promise resolves. | |
| isLoading | boolean | false | Whether the switch is in a loading state, showing a spinner inside the thumb. |
| isLabelHidden | boolean | false | Visually hides the label while keeping it accessible to screen readers. |
| description | string | Description text displayed below the label. | |
| isDisabled | boolean | false | Whether the switch is disabled. |
| size | 'sm' | 'md' | 'md' | Size variant controlling track and thumb dimensions. sm (32x20px) matches sm checkbox/radio vertical rhythm; md (40x24px, default) matches md checkbox/radio vertical rhythm. |
| htmlName | string | The HTML name attribute for the underlying checkbox input, useful for form submissions (submits "on" when the switch is on). | |
| disabledMessage | string | Explains why the switch is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the switch focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled Switch in Tooltip. Disabled controls swallow the hover events an external Tooltip needs. | |
| isOptional | boolean | false | Whether the field is optional. Mutually exclusive with isRequired. |
| isRequired | boolean | false | Whether the switch is required. Mutually exclusive with isOptional. |
| status | {type: 'warning' | 'error' | 'success', message?: string} | Status indicator with type and message. Displays a colored message box below the switch and sets aria-invalid when type is "error". | |
| onFocus | (e: FocusEvent<HTMLInputElement>) => void | Callback fired when the switch receives focus. | |
| onBlur | (e: FocusEvent<HTMLInputElement>) => void | Callback fired when the switch loses focus. | |
| labelIcon | IconType | Icon displayed before the label text. See astryx docs icons for valid semantic names. | |
| labelTooltip | string | Tooltip text shown in an info icon at the end of the label. | |
| labelPosition | 'start' | 'end' | 'end' | Which side of the switch the label appears on. "start" places the label before the switch. |
| labelSpacing | 'hug' | 'spread' | 'hug' | Spacing behavior between label and switch. "hug" places them next to each other; "spread" pushes them to opposite ends of the container (full width). |
| width | SizeValue | Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned. |
Usage guidance
- Use for settings that apply immediately; the toggle should take effect without a separate save action.
- Pair with a clear, concise label that describes the setting being controlled.
- Avoid: Use for options that require a form submission to take effect; use a checkbox instead.
- Avoid: Use a switch for multi-state values; it's strictly on/off.
- Avoid: Wrap a disabled switch in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.
Theming API
{
"targets": [
{
"className": "astryx-switch",
"visualProps": [
"size"
],
"states": [
"checked",
"disabled"
]
},
{
"className": "astryx-switch-thumb",
"visualProps": [
"size"
],
"states": [
"checked"
]
},
{
"className": "astryx-switch-field",
"visualProps": [
"labelPosition",
"labelSpacing"
]
},
{
"className": "astryx-switch-label"
}
]
}
Accessibility
- Track and thumb: The on and off tracks must each have at least 3:1 contrast with the surface behind them, and the thumb with its track. For Hover and Pointer down, measure the final colors after the tint and the pressed overlay are applied. — 3:1
Anatomy
- Field: Container arranging the switch, label, and feedback.
- Track: Pill-shaped surface that shows the off or on state.
- Thumb: Indicator that moves across the track when state changes.
- Label: Text identifying the setting controlled by the switch.
- Description: Helper text below the label.
- Spinner: Loading indicator shown inside the thumb while busy.
- Status message: Error, warning, or success message below the switch.
Examples
- Switch: A toggle switch for enabling notifications.
- Switch — Disabled: Disabled switch with label and description for gated features.
- Switch — Settings Panel: Settings panel with spread-spaced switches in a card.
- Switch — With Description: Toggle with a label and supporting description text.
- Switch — With Status: Switches with error, warning, and success validation states.