Skip to content

ComponentsForms

Switch

Turns a single setting on or off, taking effect immediately. Three sizes, an optional description, start or end label placement for settings lists, and native form submission.

React AriaSource

Installation

pnpm dlx shadcn@latest add @desyne/switch

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { Switch } from "@/components/ui/switch";
tsx
<Switch isSelected={wifi} onChange={setWifi}>
  Wi-Fi
</Switch>

Switches apply immediately

Users expect a switch to take effect the moment it's flipped, like a light switch. If the change only applies after pressing Save or Submit, use a Checkbox instead.

When to use

  • Switch — a single on/off setting that applies right away: notifications, dark mode, a feature flag.
  • Checkbox — options confirmed by a submit button, required consent, or many options in a group.
  • Toggle Button — an on/off state inside a toolbar, like bold or mute.
  • Radio Group — more than two states, or two states whose labels aren't simply "on" and "off".

Anatomy

tsx
<Switch>      {/* <label> wrapping a hidden <input type="checkbox" role="switch"> */}
  Label       {/* children */}
</Switch>
PartRendersNotes
Switch<label> + hidden <input role="switch">Root. Carries data-slot="switch" and every state attribute.
Track<span>The pill. Uses --brand when on. Sized by size.
Thumb<span>The white knob; slides to the end when on.
LabelchildrenNext to the track. With description, becomes a bold title with secondary text under it.

Examples

Sizes

md is the default. Use sm in dense lists and tables, and lg for a prominent master switch or touch-first layouts.

With description

description adds secondary text under the label and aligns the track to the first line. Use it to explain what the setting does.

Label placement

labelPlacement="start" puts the label first and pushes the switch to the far end of its container, which is the usual layout for settings rows.

States

isDisabled dims the switch and removes it from the tab order. isReadOnly keeps it focusable and announced, but ignores presses, which is useful while a change is saving.

Controlled

Use isSelected and onChange to own the value. Use defaultSelected when you only need the initial state.

Your site is live.

Without a visible label

When the label lives elsewhere in the layout, point to it with aria-labelledby (and to helper text with aria-describedby). A switch with no label at all needs an aria-label.

Dark mode

Follows your system by default.

Settings list

labelPlacement="start" plus description and some padding turns each switch into a full-width, fully clickable settings row.

Revealing options

Control the switch to show dependent fields only when the setting is on.

Saving asynchronously

Because switches apply immediately, persist the change right away. Update optimistically, set isReadOnly while the request is in flight, and roll back with a message if it fails. The status text is in an aria-live region so screen-reader users hear the outcome.

Form submission

Give the switch a name and its value ("on" by default) is submitted when it's on. Like a native checkbox, nothing is submitted when it's off, so treat a missing key as false on the server.

Switch doesn't support isRequired or validation. For a required "I agree" control, use a Checkbox.

Recipes

Notification settings

A large master switch that disables and clears the category switches under it.

Feature flags

Small switches aligned to the end of each row, labelled by the flag name via aria-labelledby, with a Badge for the release stage.

  • New editorBeta

    new-editor · Enabled for 25% of users

  • AI summariesAlpha

    ai-summaries · Internal team only

  • Usage-based billingGA

    usage-billing · Enabled for all users

Billing period toggle

A switch that flips pricing cards between monthly and yearly billing.

2 months free

Starter

$12 /month

Billed monthly

  • 3 projects
  • Basic analytics
  • Email support

Growth

$39 /month

Billed monthly

  • Unlimited projects
  • Advanced analytics
  • Priority support

Accessibility

  • Renders a native <input type="checkbox" role="switch">, visually hidden inside a <label>, so screen readers announce it as a switch that is on or off, and clicking the label toggles it.
  • Keep the label constant (e.g. "Wi-Fi"), not "Turn on Wi-Fi" / "Turn off Wi-Fi": the on/off state is announced separately.
  • A switch without children needs aria-label or aria-labelledby.
  • description is rendered inside the <label>, so it's read as part of the name. For long help text, render it outside and link it with aria-describedby.
  • If a change can fail or takes time, announce the outcome in an aria-live region.
  • The focus ring only appears for keyboard focus (data-focus-visible).

Keyboard

KeyAction
TabMoves focus to the switch
SpaceToggles the switch

Styling

Data attributes

On the Switch root (target the track or thumb with group-data-*/switch:):

AttributePresent when
data-selectedOn
data-hoveredHovered with a mouse or pen
data-pressedBeing pressed
data-focused / data-focus-visibleFocused / focused with the keyboard
data-disabledisDisabled is true
data-readonlyisReadOnly is true

Slots

data-slotElement
switchThe <label> root. Target switches from a parent with *:data-[slot=switch]:…

Color

The track uses the --brand theme variable when on. Override it for a one-off color:

tsx
<Switch className="[--brand:var(--color-emerald-600)]">Online</Switch>

Render props

className and children accept a function of the switch state:

tsx
<Switch>{({ isSelected }) => (isSelected ? "Visible to everyone" : "Hidden")}</Switch>

API Reference

Switch

Prop

Type

Also accepts every prop of React Aria's Switch.