Examples
On and off
A binary preference communicates its result through a clear label.
Receive a weekly summary of workspace activity.
Uncontrolled state with a default value.
Disabled while the workspace is in use.
Read-only state while inherited from the organisation.
Use a full sentence when the setting has a high-impact consequence.
Controlled, uncontrolled, disabled, read-only, and long-label switch states
"use client";
import { useState } from "react";
import { Field, FieldDescription, FieldLabel } from "@phuctech/ui/components/field";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [emailUpdates, setEmailUpdates] = useState(true);
return (
<Field>
<FieldLabel htmlFor="email-updates">Email updates</FieldLabel>
<Switch
id="email-updates"
checked={emailUpdates}
onCheckedChange={setEmailUpdates}
aria-describedby="email-updates-description"
/>
<FieldDescription id="email-updates-description">
Receive a weekly summary of workspace activity.
</FieldDescription>
</Field>
);
}
Sizes
Use sm, md, or lg to match the surrounding form density.
Small, medium, and large switch sizes
import { Switch } from "@phuctech/ui/components/switch";
const sizes = ["sm", "md", "lg"] as const;
export function Example() {
return (
<div className="flex flex-wrap items-center gap-6">
{sizes.map((size) => (
<div key={size} className="flex flex-col items-center gap-2">
<Switch size={size} defaultChecked aria-label={`${size} switch`} />
<span className="text-xs text-muted-foreground">{size}</span>
</div>
))}
</div>
);
}
Icon in the thumb
Reinforce the on and off meaning with an icon that moves with the thumb.
Switch with a sun and moon icon inside the thumb
"use client";
import { useState } from "react";
import { Moon, Sun } from "@phuctech/ui/icons";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [darkMode, setDarkMode] = useState(false);
return (
<Switch
size="lg"
checked={darkMode}
onCheckedChange={setDarkMode}
aria-label="Dark mode"
checkedIcon={<Moon aria-hidden="true" />}
uncheckedIcon={<Sun aria-hidden="true" />}
/>
);
}
Controlled state
Feature state updates immediately when the switch changes.
Controlled Switch
"use client";
import { useState } from "react";
import { Field, FieldDescription, FieldLabel } from "@phuctech/ui/components/field";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [emailUpdates, setEmailUpdates] = useState(true);
return (
<Field>
<FieldLabel htmlFor="email-updates">Email updates</FieldLabel>
<Switch
id="email-updates"
checked={emailUpdates}
onCheckedChange={setEmailUpdates}
aria-describedby="email-updates-description"
/>
<FieldDescription id="email-updates-description">
Receive a weekly summary of workspace activity.
</FieldDescription>
</Field>
);
}
Uncontrolled default
defaultChecked supplies a local initial value without mirrored state.
Uncontrolled Switch with a default value
"use client";
import { useState } from "react";
import { Field, FieldDescription, FieldLabel } from "@phuctech/ui/components/field";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [emailUpdates, setEmailUpdates] = useState(true);
return (
<Field>
<FieldLabel htmlFor="email-updates">Email updates</FieldLabel>
<Switch
id="email-updates"
checked={emailUpdates}
onCheckedChange={setEmailUpdates}
aria-describedby="email-updates-description"
/>
<FieldDescription id="email-updates-description">
Receive a weekly summary of workspace activity.
</FieldDescription>
</Field>
);
}
Disabled and read-only
Unavailable and inherited states remain understandable without relying on color.
Disabled and read-only Switch states
"use client";
import { useState } from "react";
import { Field, FieldDescription, FieldLabel } from "@phuctech/ui/components/field";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [emailUpdates, setEmailUpdates] = useState(true);
return (
<Field>
<FieldLabel htmlFor="email-updates">Email updates</FieldLabel>
<Switch
id="email-updates"
checked={emailUpdates}
onCheckedChange={setEmailUpdates}
aria-describedby="email-updates-description"
/>
<FieldDescription id="email-updates-description">
Receive a weekly summary of workspace activity.
</FieldDescription>
</Field>
);
}
Native form value
name, value, and required integrate with a native form when needed.
Switch with native form attributes
"use client";
import { useState } from "react";
import { Field, FieldDescription, FieldLabel } from "@phuctech/ui/components/field";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [emailUpdates, setEmailUpdates] = useState(true);
return (
<Field>
<FieldLabel htmlFor="email-updates">Email updates</FieldLabel>
<Switch
id="email-updates"
checked={emailUpdates}
onCheckedChange={setEmailUpdates}
aria-describedby="email-updates-description"
/>
<FieldDescription id="email-updates-description">
Receive a weekly summary of workspace activity.
</FieldDescription>
</Field>
);
}
Switch playground
Change a prop and the live preview and copied example stay in sync.
Switch preview with the selected props
"use client";
import { useState } from "react";
import { Switch } from "@phuctech/ui/components/switch";
import { Moon, Sun } from "@phuctech/ui/icons";
export function Example() {
const [enabled, setEnabled] = useState(true);
return (
<Switch
checked={enabled}
onCheckedChange={setEnabled}
aria-label="Feature toggle"
checkedIcon={<Moon aria-hidden="true" />}
uncheckedIcon={<Sun aria-hidden="true" />}
/>
);
}When to use
Use Switch when the setting has two states and can be changed without an additional submit step. Use a checkbox when several options are submitted together, and use a radio group when the person must choose one option from several.
Usage
"use client";
import { useState } from "react";
import { Field, FieldDescription, FieldLabel } from "@phuctech/ui/components/field";
import { Switch } from "@phuctech/ui/components/switch";
export function Example() {
const [emailUpdates, setEmailUpdates] = useState(true);
return (
<Field>
<FieldLabel htmlFor="email-updates">Email updates</FieldLabel>
<Switch
id="email-updates"
checked={emailUpdates}
onCheckedChange={setEmailUpdates}
aria-describedby="email-updates-description"
/>
<FieldDescription id="email-updates-description">
Receive a weekly summary of workspace activity.
</FieldDescription>
</Field>
);
}
Controlled state is the right choice when the feature saves the setting, reflects a server value, or needs to coordinate other UI. Use defaultChecked for a local uncontrolled preference. The Switch component does not read or write theme state on its own.
States
API
Switch wraps Base UI's Switch.Root and renders its thumb. It forwards the native and Base UI props below; the thumb is part of the component and is not supplied as children.
Tokens
Accessibility
- Give every switch a visible label through
FieldLabelor a concisearia-label. - Connect supporting instructions with
aria-describedby; do not rely on the track color alone. - The control is keyboard-operable with Space and exposes its state through the native
switchrole. - Keep disabled and read-only states understandable in both light and dark themes, and preserve the visible focus ring.
Do and don’t
Use a verb-led label such as “Email updates”, keep the state local to the feature, and provide a description when the consequence is not obvious.
Use a switch for navigation, hide the only label in a tooltip, or make a setting appear changed before its state is known.
Related
- Field connects a label and description to the control.
- Colors documents the semantic roles used for both themes.
- Accessibility documents focus, contrast, and reduced-motion checks.