Switch
A composable switch control for turning a setting on or off.
API Reference
Original primitive API
Behavior, accessibility details, and low-level props are documented by Base UI.
Basic
import { Switch, SwitchField, SwitchLabel } from "moduix";export function SwitchDemo() { return ( <SwitchField> <Switch defaultChecked /> <SwitchLabel>Enable notifications</SwitchLabel> </SwitchField> );}Full list of component variables available for project-level overrides.
| Property | Default | Description |
|---|---|---|
| --switch-bg | var(--color-muted) | Controls unchecked background color. |
| --switch-bg-checked | var(--color-primary) | Controls checked background color. |
| --switch-bg-checked-hover | var(--switch-bg-checked, var(--color-primary)) | Controls checked hover background color. |
| --switch-bg-hover | var(--color-accent) | Controls unchecked hover background color. |
| --switch-border-color | var(--color-border) | Controls unchecked border color. |
| --switch-border-color-checked | var(--color-primary) | Controls checked border color. |
| --switch-border-width | var(--border-width-sm) | Controls switch border width. |
| --switch-disabled-opacity | var(--opacity-disabled) | Controls disabled opacity. |
| --switch-focus-ring-color | var(--color-ring) | Controls focus ring color. |
| --switch-focus-ring-offset | var(--border-width-sm) | Controls focus ring offset. |
| --switch-focus-ring-width | var(--border-width-sm) | Controls focus ring width. |
| --switch-gap | var(--spacing-2) | Controls spacing between switch and label. |
| --switch-height-xs | 1rem | Controls switch height for the xs size. |
| --switch-height-sm | 1.25rem | Controls switch height for the sm size. |
| --switch-height-md | 1.5rem | Controls switch height for the md size. |
| --switch-height-lg | 1.75rem | Controls switch height for the lg size. |
| --switch-height-xl | 2rem | Controls switch height for the xl size. |
| --switch-label-color | var(--color-foreground) | Controls label text color. |
| --switch-label-font-size | var(--text-sm) | Controls label font size. |
| --switch-label-font-weight | var(--weight-medium) | Controls label font weight. |
| --switch-label-line-height | var(--line-height-text-sm) | Controls label line height. |
| --switch-padding | 0.125rem | Controls inner switch padding. |
| --switch-radius | var(--radius-full) | Controls switch corner radius. |
| --switch-thumb-bg | var(--color-background) | Controls thumb background color for both states. |
| --switch-thumb-bg-unchecked | var(--switch-thumb-bg, var(--color-background)) | Controls unchecked thumb background color. |
| --switch-thumb-bg-checked | var(--switch-thumb-bg, var(--color-primary-foreground)) | Controls checked thumb background color. |
| --switch-thumb-border-color | transparent | Controls thumb border color. |
| --switch-thumb-border-width | 0 | Controls thumb border width. |
| --switch-thumb-color | var(--color-muted) | Controls thumb content color. |
| --switch-thumb-color-unchecked | var(--switch-thumb-color, var(--color-muted)) | Controls unchecked thumb content color. |
| --switch-thumb-color-checked | var(--switch-thumb-color, var(--color-primary)) | Controls checked thumb content color. |
| --switch-thumb-icon-size | 65% | Controls custom thumb icon size. |
| --switch-thumb-radius | var(--radius-full) | Controls thumb corner radius. |
| --switch-thumb-shadow | var(--shadow-sm) | Controls thumb shadow. |
| --switch-thumb-size-xs | 0.625rem | Controls thumb size for the xs switch size. |
| --switch-thumb-size-sm | 0.875rem | Controls thumb size for the sm switch size. |
| --switch-thumb-size-md | 1.125rem | Controls thumb size for the md switch size. |
| --switch-thumb-size-lg | 1.375rem | Controls thumb size for the lg switch size. |
| --switch-thumb-size-xl | 1.625rem | Controls thumb size for the xl switch size. |
| --switch-thumb-transition | var(--switch-transition, var(--transition-default)) | Controls thumb movement transition timing. |
| --switch-thumb-translate | var(--switch-thumb-translate-default) | Controls checked thumb translation distance. |
| --switch-transition | var(--transition-default) | Controls state transition timing. |
| --switch-width-xs | 1.75rem | Controls switch width for the xs size. |
| --switch-width-sm | 2rem | Controls switch width for the sm size. |
| --switch-width-md | 2.5rem | Controls switch width for the md size. |
| --switch-width-lg | 3rem | Controls switch width for the lg size. |
| --switch-width-xl | 3.5rem | Controls switch width for the xl size. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --switch-bg | var(--color-muted) | Controls unchecked background color. | |
| --switch-bg-checked | var(--color-primary) | Controls checked background color. | |
| --switch-bg-hover | var(--color-accent) | Controls unchecked hover background color. | |
| --switch-border-color | var(--color-border) | Controls unchecked border color. | |
| --switch-border-color-checked | var(--color-primary) | Controls checked border color. | |
| --switch-focus-ring-color | var(--color-ring) | Controls focus ring color. | |
| --switch-thumb-bg | var(--color-background) | Controls thumb background color. | |
| --switch-thumb-color | var(--color-muted) | Controls thumb content color. | |
| --switch-label-color | var(--color-foreground) | Controls label text color. |
Anatomy
Switch renders the interactive control and its internal thumb automatically. Use
SwitchField and SwitchLabel when the label wraps the control; use a native label with id
when the label is a sibling.
SwitchField
|- Switch
| `- internal thumb
`- SwitchLabel<SwitchField>
<Switch defaultChecked />
<SwitchLabel>Enable notifications</SwitchLabel>
</SwitchField>| Part | Role |
|---|---|
Switch | Root state machine and visible track. Controls checked, defaultChecked, form props, and size. |
| internal thumb | Movable visual marker rendered by Switch. Customize content with thumb and styles with classNames.thumb. |
SwitchField | Optional wrapping label for the common inline field pattern. |
SwitchLabel | Optional text label slot styled to match the switch field. |
Switch does not use portal-like service layers such as portal, positioner, backdrop,
or viewport. In most cases, keep the internal thumb and style the root Switch,
SwitchField, and SwitchLabel.
Composition
Use Switch directly for the control. Pass className to style the track and classNames.thumb
to style the internal thumb. Pass thumb when the thumb needs custom content, such as an icon.
Use nativeButton with render={<button />} for sibling label patterns. Use the render
callback when a native button switch needs to live inside a wrapping label without invalid nested
button markup.
Examples
Sizes
Use size to align the switch with different form densities.
Override --switch-width-{size}, --switch-height-{size}, and --switch-thumb-size-{size} when a specific size needs custom dimensions.
import { Switch, SwitchField, SwitchLabel } from "moduix";import styles from "./switch-demo.module.css";export function SwitchSizesDemo() { return ( <div className={styles.stack}> <SwitchField> <Switch size="xs" defaultChecked /> <SwitchLabel>Extra-small</SwitchLabel> </SwitchField> <SwitchField> <Switch size="sm" defaultChecked /> <SwitchLabel>Small</SwitchLabel> </SwitchField> <SwitchField> <Switch size="md" defaultChecked /> <SwitchLabel>Medium</SwitchLabel> </SwitchField> <SwitchField> <Switch size="lg" defaultChecked /> <SwitchLabel>Large</SwitchLabel> </SwitchField> <SwitchField> <Switch size="xl" defaultChecked /> <SwitchLabel>Extra-large</SwitchLabel> </SwitchField> </div> );}.stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--spacing-3);}Disabled
Use disabled to prevent interaction while preserving the current setting state in the UI.
import { Switch, SwitchField, SwitchLabel } from "moduix";import styles from "./switch-demo.module.css";export function DisabledSwitchDemo() { return ( <div className={styles.stack}> <SwitchField> <Switch disabled /> <SwitchLabel>Enable dark mode</SwitchLabel> </SwitchField> <SwitchField> <Switch defaultChecked disabled /> <SwitchLabel>Keep me signed in</SwitchLabel> </SwitchField> </div> );}.stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--spacing-3);}Controlled
Control checked from React state when the switch needs to coordinate with other UI.
import { Switch, SwitchField, SwitchLabel } from "moduix";import { useState } from "react";import styles from "./switch-demo.module.css";export function ControlledSwitchDemo() { const [checked, setChecked] = useState(true); return ( <div className={styles.stack}> <SwitchField> <Switch checked={checked} onCheckedChange={setChecked} /> <SwitchLabel>{checked ? "On" : "Off"}</SwitchLabel> </SwitchField> <span className={styles.hint}>Current value: {String(checked)}</span> </div> );}.stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--spacing-3);}.hint { color: var(--color-muted-foreground); font-size: var(--text-xs); line-height: var(--line-height-text-xs);}Custom Icon
Pass thumb to render any icon from your application or icon library inside the internal thumb.
import { Switch, SwitchField, SwitchLabel } from "moduix";import type { ComponentProps } from "react";import styles from "./switch-demo.module.css";function PowerIcon(props: ComponentProps<"svg">) { return ( <svg viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false" {...props}> <path d="M8 2.5V7M5.1 4.3A5 5 0 1 0 10.9 4.3" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" /> </svg> );}export function CustomIconSwitchDemo() { return ( <SwitchField> <Switch defaultChecked thumb={<PowerIcon />} classNames={{ thumb: styles.customIconThumb }} /> <SwitchLabel>Use custom thumb icon</SwitchLabel> </SwitchField> );}.customIconThumb { --switch-thumb-color-checked: var(--color-primary); --switch-thumb-color-unchecked: var(--color-muted-foreground); --switch-thumb-icon-size: 0.75rem;}Class Names
Pass className to the field, root, and label slots when styling with CSS Modules, Tailwind CSS, or CSS-in-JS.
import { Switch, SwitchField, SwitchLabel } from "moduix";import styles from "./switch-demo.module.css";export function StyledSwitchDemo() { return ( <SwitchField className={styles.customField}> <Switch className={styles.customSwitch} defaultChecked /> <SwitchLabel className={styles.customLabel}>Styled with className</SwitchLabel> </SwitchField> );}.customField { gap: var(--spacing-3);}.customSwitch { --switch-bg: color-mix(in oklab, var(--color-primary) 12%, transparent); --switch-bg-hover: color-mix(in oklab, var(--color-primary) 18%, transparent); --switch-bg-checked: var(--color-primary); --switch-border-color: color-mix(in oklab, var(--color-primary) 45%, var(--color-border));}.customLabel { color: var(--color-primary);}Sibling Label
Use nativeButton with render={<button />} for the sibling label pattern with htmlFor and id.
import { Switch } from "moduix";import { useId } from "react";import styles from "./switch-demo.module.css";export function SiblingLabelSwitchDemo() { const id = useId(); return ( <div className={styles.siblingRow}> <Switch nativeButton render={<button />} id={id} defaultChecked /> <label htmlFor={id} className={styles.label}> Receive product updates </label> </div> );}.siblingRow { display: flex; align-items: center; gap: var(--switch-gap, var(--spacing-2));}.label { color: var(--switch-label-color, var(--color-foreground)); font-size: var(--switch-label-font-size, var(--text-sm)); font-weight: var(--switch-label-font-weight, var(--weight-medium)); line-height: var(--switch-label-line-height, var(--line-height-text-sm));}Native Button Render Callback
Use the render callback when a native button switch needs to be wrapped by a label without producing invalid nested button markup.
import { Switch } from "moduix";import styles from "./switch-demo.module.css";export function NativeButtonSwitchDemo() { return ( <Switch defaultChecked nativeButton render={(buttonProps) => ( <label className={styles.siblingRow}> <button {...buttonProps} /> <span className={styles.label}>Enable reminders</span> </label> )} /> );}.siblingRow { display: flex; align-items: center; gap: var(--switch-gap, var(--spacing-2));}.label { color: var(--switch-label-color, var(--color-foreground)); font-size: var(--switch-label-font-size, var(--text-sm)); font-weight: var(--switch-label-font-weight, var(--weight-medium)); line-height: var(--switch-label-line-height, var(--line-height-text-sm));}Form Integration
Use Field when the switch participates in form labeling, validation, or submission.
import { Field, FieldLabel, Switch, SwitchLabel,} from "moduix";export function SwitchFormDemo() { return ( <Field name="notifications"> <FieldLabel> <Switch defaultChecked /> <SwitchLabel>Notifications</SwitchLabel> </FieldLabel> </Field> );}