Skip to content

Switch

Turns a setting on or off immediately. Use it where the change takes effect the moment it is made.

tsx
import { Switch } from 'neba';

<Switch label="Email alerts" defaultChecked />;

Props

PropTypeDefaultDescription
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Height and type scale
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
labelReactNodeThe label, wired to the control by Base UI's Field
descriptionReactNodeHelper text
errorReactNodeError message. Its presence turns the control invalid and re-points the colour family at danger
invalidboolean!!errorForces the invalid state without a message, for when a form library owns validity
checkedbooleanWhether it is on. Use with onCheckedChange for a controlled switch
defaultCheckedbooleanfalseThe initial state, for an uncontrolled switch
onCheckedChange(checked: boolean, details) => voidCalled when the switch is turned on or off
labelPlacement'start' | 'end''end'Which side the label sits on. `start` is for a settings list, where the switches line up on the right
namestringIdentifies the field when a form is submitted
readOnlybooleanfalseShown but not changeable. Keeps its colour and edge, drains the saturation
disabledbooleanfalseUnavailable. Drops the colour family for neutral grey
classNamesNebaSlots<'label' | 'control' | 'description' | 'error' | 'thumb'>Class names for the parts behind the root. The root itself is className, so there is no root key

If there is a Save button underneath and the value is submitted with a form, use Checkbox instead. That is what separates the two.

Examples

checked and onCheckedChange

checked with onCheckedChange makes it controlled; defaultChecked makes it uncontrolled.

disabled · readOnly

labelPlacement

end (the default) puts the label after the control, so it reads as a caption. start suits a settings list, where the labels form a left column and the switches line up on the right.

size

classNames

className lands on the field wrapper, not on the track. The track and the thumb are reached through classNames.

tsx
<Switch label="Email alerts" classNames={{ control: 'w-14', thumb: 'rounded-sm' }} />

The slots are label, control, thumb, description and error. control is the track (the pill that fills when the switch is on), and thumb is the disc that travels across it. See prop conventions for how a class name you pass resolves against the component's own.

Accessibility

  • Renders role="switch" with a hidden <input> beside it.
  • The label is wired to the control, so clicking the text flips it.
  • Without a label, give it an aria-label.
  • The thumb's travel becomes instant under prefers-reduced-motion.

Released under the MIT License