Switch
Instant boolean toggles. Immediate feedback. Modern UX.
Deploy on push to main
Preview URL for every pull request
Maintenance mode
1import { Switch } from '@/components/ui/switch';23const settings = [4 { id: 'auto-deploy', label: 'Deploy on push to main', defaultChecked: true },5 { id: 'preview-urls', label: 'Preview URL for every pull request', defaultChecked: true },6 { id: 'maintenance', label: 'Maintenance mode', defaultChecked: false },7];89export function Default() {10 return (11 <div className="border-border bg-card w-full max-w-md rounded-xl border">12 {settings.map((setting) => (13 <div14 key={setting.id}15 className="border-border flex items-center justify-between gap-4 border-b p-3.5 last:border-b-0"16 >17 <span id={`${setting.id}-label`} className="text-sm">18 {setting.label}19 </span>20 <Switch aria-labelledby={`${setting.id}-label`} defaultChecked={setting.defaultChecked} />21 </div>22 ))}23 </div>24 );25}
Installation
Copy and paste the following code into your project.
1'use client';23import * as React from 'react';4import { cn } from '../lib/cn';56export interface SwitchProps extends React.InputHTMLAttributes<HTMLInputElement> {7 onCheckedChange?: (checked: boolean) => void;8}910function Switch({11 className,12 onCheckedChange,13 onChange,14 ref,15 ...props16}: SwitchProps & { ref?: React.Ref<HTMLInputElement> }) {17 return (18 <label19 className={cn(20 'has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-background border-border/60 relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border transition-colors has-disabled:cursor-not-allowed has-disabled:opacity-40 has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-offset-1 has-[:focus-visible]:outline-none',21 'before:absolute before:-inset-x-1 before:-inset-y-1 before:rounded-full pointer-coarse:before:-inset-x-1.5 pointer-coarse:before:-inset-y-3',22 className,23 )}24 >25 <input26 type="checkbox"27 role="switch"28 ref={ref}29 className="peer sr-only"30 onChange={(e) => {31 onChange?.(e);32 onCheckedChange?.(e.target.checked);33 }}34 {...props}35 />36 <div37 aria-hidden="true"38 className="bg-muted peer-checked:bg-primary/70 pointer-events-none absolute inset-x-0 h-full w-full rounded-full transition-colors"39 />40 <span41 aria-hidden="true"42 className="bg-foreground/60 peer-checked:bg-primary-foreground pointer-events-none z-10 block h-3.5 w-3.5 translate-x-0.5 rounded-full shadow-sm ring-0 transition-transform peer-checked:translate-x-4 motion-reduce:transition-none"43 />44 </label>45 );46}47Switch.displayName = 'Switch';4849export { Switch };
Update the import paths to match your project structure.
Anatomy
1import { Switch } from '@/components/ui/switch';
1<Switch id="airplane-mode" />
Variants
Default
Deploy on push to main
Preview URL for every pull request
Maintenance mode
1import { Switch } from '@/components/ui/switch';23const settings = [4 { id: 'auto-deploy', label: 'Deploy on push to main', defaultChecked: true },5 { id: 'preview-urls', label: 'Preview URL for every pull request', defaultChecked: true },6 { id: 'maintenance', label: 'Maintenance mode', defaultChecked: false },7];89export function Default() {10 return (11 <div className="border-border bg-card w-full max-w-md rounded-xl border">12 {settings.map((setting) => (13 <div14 key={setting.id}15 className="border-border flex items-center justify-between gap-4 border-b p-3.5 last:border-b-0"16 >17 <span id={`${setting.id}-label`} className="text-sm">18 {setting.label}19 </span>20 <Switch aria-labelledby={`${setting.id}-label`} defaultChecked={setting.defaultChecked} />21 </div>22 ))}23 </div>24 );25}
With Label
Switch component alongside a label for context.
Text the on call engineer when a service goes down.
Warn the workspace owner at 80% of the plan limit.
1import { Label } from '@/components/ui/label';2import { Switch } from '@/components/ui/switch';34const preferences = [5 {6 id: 'incident-sms',7 label: 'Incident SMS',8 hint: 'Text the on call engineer when a service goes down.',9 defaultChecked: true,10 },11 {12 id: 'usage-alerts',13 label: 'Usage alerts',14 hint: 'Warn the workspace owner at 80% of the plan limit.',15 defaultChecked: false,16 },17];1819export function WithLabel() {20 return (21 <div className="flex w-full max-w-md flex-col gap-4">22 {preferences.map((preference) => (23 <div key={preference.id} className="flex items-start justify-between gap-4">24 <div className="flex flex-col gap-1">25 <Label htmlFor={preference.id}>{preference.label}</Label>26 <span className="text-muted-foreground text-xs">{preference.hint}</span>27 </div>28 <Switch29 id={preference.id}30 defaultChecked={preference.defaultChecked}31 className="mt-0.5"32 />33 </div>34 ))}35 </div>36 );37}
Disabled
A disabled state stops interactions.
Always on for production projects.
Available on the Enterprise plan.
1import { Label } from '@/components/ui/label';2import { Switch } from '@/components/ui/switch';34const features = [5 {6 id: 'nightly-backups',7 label: 'Nightly backups',8 hint: 'Always on for production projects.',9 defaultChecked: true,10 },11 {12 id: 'audit-log',13 label: 'Audit log export',14 hint: 'Available on the Enterprise plan.',15 defaultChecked: false,16 },17];1819export function Disabled() {20 return (21 <div className="flex w-full max-w-md flex-col gap-4">22 {features.map((feature) => (23 <div key={feature.id} className="flex items-start justify-between gap-4">24 <div className="flex flex-col gap-1">25 <Label htmlFor={feature.id} className="opacity-40">26 {feature.label}27 </Label>28 <span className="text-muted-foreground text-xs">{feature.hint}</span>29 </div>30 <Switch31 id={feature.id}32 defaultChecked={feature.defaultChecked}33 className="mt-0.5"34 disabled35 />36 </div>37 ))}38 </div>39 );40}
API Reference
Switch
| Prop | Type | Default | Description |
|---|---|---|---|
onCheckedChange | (checked: boolean) => void | - | Callback when checked state changes |
disabled | boolean | - | Disables the switch |
Found something to improve?
Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.