Gesso
Forms

Switch

Binary flag for display properties and settings.

Switch turns a setting on or off the moment it is flipped, like showing a column or muting a channel. Its track fills with bg-tone when it is on and the thumb slides across, and it comes in two sizes. It has no label of its own, so pair it with Field and keep the label beside it.

Fix the login redirectAna Silva

Usage

import { Field, FieldLabel, FieldRow } from '@/components/ui/field';
import { Switch } from '@/components/ui/switch';

<Field>
  <FieldRow>
    <Switch id="show-assignee" checked={showAssignee} onCheckedChange={setShowAssignee} />
    <FieldLabel htmlFor="show-assignee">Show assignee</FieldLabel>
  </FieldRow>
</Field>

onCheckedChange hands you a boolean, so it can go straight to a state setter, and defaultChecked lets the switch keep the state instead. A plain <label htmlFor> names it just as well when the switch is not in a Field.

On, off, and disabled

On fills the track with bg-tone, which is the primary unless you set a tone. Off fills it with bg-track, a contrast wash that reads over any surface, and the thumb is bg-surface-raised in both. Disabled fades the track, to the primary at 32% when it is on and the contrast ink at 16% when it is off, and turns the thumb into the contrast ink at 64%, so a disabled switch still shows which way it is set.

Two sizes

default is a 36 by 20px track with a 16px thumb, and sm is 24 by 14px with a 12px thumb, for a dense row or a table cell. Both take the same 44px target on a touch screen.

In a form

Inside a <form>, Switch renders a hidden native checkbox and keeps it in step, so the form sends value under name while the switch is on and nothing while it is off. value is "on" unless you set one, required goes on the hidden input too, and a form reset puts the switch back where it started. A switch reads as live, though, so a reader expects it to apply without a submit. When the change waits for a Save button, use a Checkbox.

A development warning in forms

In development, toggling a Switch inside a <form> while a Popover is open logs React's "flushSync was called from inside a lifecycle method" once. Inside a form, Radix Switch keeps its hidden input in sync by dispatching a click from an effect, and the open Popover's outside-click handler calls flushSync on it. The Popover still closes and the Switch still toggles, and production builds of React leave the warning out, so there is nothing to change in your code.

Props

Switch takes every prop of the Radix Switch, such as checked, defaultChecked, onCheckedChange, name, value, required, and disabled, plus these.

PropTypeDefaultWhat it does
tone"neutral" | "primary" | "support" | "danger" | "success" | "warning" | "info" | "inherit""primary"The hue of the checked track. inherit takes the tone of the surrounding scope.
size"default" | "sm""default"A 36 by 20px track with a 16px thumb, or 24 by 14px with a 12px thumb. Both reach 44px on a touch screen, and the size lands in data-size for your own styles.

Accessibility

  • It renders a <button type="button" role="switch"> with aria-checked, so assistive tech announces a switch and whether it is on.
  • Space and Enter both flip it, as on any button.
  • The name comes from the FieldLabel whose htmlFor matches the switch's id. A switch with no visible label needs an aria-label.
  • required sets aria-required, and aria-invalid reports the switch as invalid and turns the track's border border-destructive.
  • Focus shows a 2px ring on :focus-visible only, offset from the track, in the contrast ink rather than the brand color.
  • In forced colors the track gets a ButtonText border. Off, the thumb is ButtonText, and on, the track fills with Highlight under a HighlightText thumb. Disabled, the border and an off thumb turn GrayText, and an on track fills with GrayText under a Canvas thumb, so a disabled switch still shows which way it is set. Focus keeps a transparent outline that the system paints.
  • The track is 36 by 20px, or 24 by 14px at sm. On a coarse pointer, such as a touch screen, an invisible 44px square centered on it takes the tap too, and the label is a target as well. With a mouse the extra area is off.
  • disabled sets the native disabled attribute, so the switch leaves the tab order, is reported as unavailable, and the FieldLabel beside it dims.
  • Under reduced motion the thumb jumps across instead of sliding, and the track changes color without a transition.

When to use something else

  • Checkbox for a yes or no answer that a form submits with its other fields, or for picking several items from a list.
  • Radio group when the setting has a few values rather than on and off, like a row density, and Select when it has a long list of them.
  • Toggle for a toolbar button that stays pressed, like bold, which has no label beside it and no form value.

Install

shadcn add @gesso/switch

On this page