Switch
A toggle whose thumb stretches as you press it.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/switch.jsoncomponents/examples/switch-demo.tsx
"use client";
import * as React from "react";
import { Switch } from "@/components/ui/switch";
const settings = [
{ id: "digest", label: "Weekly digest", hint: "A Monday summary of what changed" },
{ id: "mentions", label: "Mentions", hint: "When someone @mentions you" },
{ id: "deploys", label: "Deploy alerts", hint: "Failed or rolled-back deploys" },
];
export default function SwitchDemo() {
const [on, setOn] = React.useState<Record<string, boolean>>({ digest: true, mentions: true, deploys: false });
return (
<div className="w-full max-w-sm divide-y divide-border rounded-xl border border-border bg-card">
{settings.map((s) => (
<label key={s.id} className="flex items-center justify-between gap-4 p-4">
<span>
<span className="block text-sm font-medium">{s.label}</span>
<span className="block text-xs text-muted-foreground">{s.hint}</span>
</span>
<Switch checked={on[s.id]} onCheckedChange={(v) => setOn((o) => ({ ...o, [s.id]: v }))} aria-label={s.label} />
</label>
))}
</div>
);
}When to use
- Settings that take effect immediately, like enabling notifications.
When not to use
- Choices that apply on Save: use a checkbox.
- More than two states: use a segmented control or select.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/switch.json1Install the dependencies
npm install motion2Copy the source into your project
components/ui/switch.tsx"use client"; import * as React from "react"; import { motion } from "motion/react"; import { cn } from "@/lib/utils"; export interface SwitchProps { checked?: boolean; defaultChecked?: boolean; onCheckedChange?: (checked: boolean) => void; disabled?: boolean; size?: "sm" | "md"; id?: string; className?: string; "aria-label"?: string; } const dims = { sm: { w: 36, h: 20, thumb: 14 }, md: { w: 46, h: 26, thumb: 20 }, }; export function Switch({ checked, defaultChecked = false, onCheckedChange, disabled, size = "md", id, className, ...aria }: SwitchProps) { const [internal, setInternal] = React.useState(defaultChecked); const on = checked ?? internal; const [pressed, setPressed] = React.useState(false); const d = dims[size]; const pad = (d.h - d.thumb) / 2; // The thumb stretches while pressed, toward the side it will travel to. const thumbW = pressed ? d.thumb + 6 : d.thumb; const x = on ? d.w - pad - thumbW : pad; const toggle = () => { if (disabled) return; if (checked === undefined) setInternal(!on); onCheckedChange?.(!on); }; return ( <button type="button" role="switch" id={id} aria-checked={on} aria-label={aria["aria-label"]} disabled={disabled} onClick={toggle} onPointerDown={() => setPressed(true)} onPointerUp={() => setPressed(false)} onPointerLeave={() => setPressed(false)} onKeyDown={(e) => e.key === " " && setPressed(true)} onKeyUp={() => setPressed(false)} style={{ width: d.w, height: d.h }} className={cn( "relative shrink-0 rounded-full outline-none transition-colors duration-200", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", "disabled:cursor-not-allowed disabled:opacity-40", on ? "bg-primary" : "bg-input", className, )} > <motion.span aria-hidden className="absolute rounded-full bg-background shadow-sm" style={{ top: pad, left: 0, height: d.thumb }} initial={false} animate={{ x, width: thumbW }} transition={{ type: "spring", stiffness: 600, damping: 34 }} /> </button> ); }
Usage
Import it from @/components/ui/switch after installing.
example.tsx
import * as React from "react";
import { Switch } from "@/components/ui/switch";
export function DigestSetting() {
const [on, setOn] = React.useState(true);
return (
<label className="flex items-center justify-between gap-4">
<span>Weekly digest</span>
<Switch checked={on} onCheckedChange={setOn} aria-label="Weekly digest" />
</label>
);
}API reference
Switch
A toggle button whose thumb stretches while pressed.
| Prop | Type | Default | Description |
|---|---|---|---|
| checked / defaultChecked | boolean | – | Controlled or uncontrolled state. |
| onCheckedChange | (checked: boolean) => void | – | Called on toggle. |
| size | "sm" | "md" | "md" | 36×20 or 46×26 px. |
| disabled | boolean | – | Dims and blocks interaction. |
Keyboard interactions
| Keys | Action |
|---|---|
| SpaceorEnter | Toggle |
Accessibility
- Native button with role="switch" and aria-checked.
- Pair with visible text or an aria-label.
Motion
- The thumb widens 6px toward its destination while pressed, then springs across (stiffness 600).
Responsive behavior
- Fixed size; aligns to the baseline of a row of text.
Performance
- One animated element.
Notes for AI
- Don't use for form fields that submit later; use Checkbox.
This page as Markdown, for assistants and coding agents:
/components/switch/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt