Switch
FormsAn accessible on/off toggle built on Radix Switch — keyboard operable, with a smooth animated thumb.
"use client";
import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
export function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer inline-flex h-6 w-11 shrink-0 items-center rounded-full border border-transparent transition-colors outline-none",
"focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"disabled:cursor-not-allowed disabled:opacity-50",
"data-[state=checked]:bg-accent data-[state=unchecked]:bg-surface-2",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
className={cn(
"pointer-events-none block size-5 rounded-full bg-white shadow-sm ring-0 transition-transform",
"data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0.5",
)}
/>
</SwitchPrimitive.Root>
);
}Overview
An on/off toggle built on Radix Switch — the right control for an immediate, binary setting that applies without a separate "save" step (think dark mode or notifications).
Controlled vs uncontrolled
Use defaultChecked for simple, uncontrolled usage. For controlled state, pair
checked with onCheckedChange:
<Switch checked={on} onCheckedChange={setOn} />
Labeling
A switch needs an accessible name. The simplest approach is to wrap it in a
<label> alongside its text, so clicking the label toggles the control.
Accessibility
Radix renders the correct role="switch" and aria-checked state, and the
control is fully keyboard operable (Space / Enter) with a visible focus ring.
Installation
npx shadcn@latest add https://ui.saumyarex.xyz/r/switch.json1. Install dependencies
npm install @radix-ui/react-switch clsx tailwind-merge2. Copy the source into your project
"use client";
import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
export function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer inline-flex h-6 w-11 shrink-0 items-center rounded-full border border-transparent transition-colors outline-none",
"focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"disabled:cursor-not-allowed disabled:opacity-50",
"data-[state=checked]:bg-accent data-[state=unchecked]:bg-surface-2",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
className={cn(
"pointer-events-none block size-5 rounded-full bg-white shadow-sm ring-0 transition-transform",
"data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0.5",
)}
/>
</SwitchPrimitive.Root>
);
}import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/** Merge conditional class names and resolve Tailwind conflicts. */
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
checked / defaultChecked | boolean | — | Controlled / uncontrolled checked state (Radix Switch). |
onCheckedChange | (checked: boolean) => void | — | Fires when the state changes. |
disabled | boolean | false | Prevents interaction. |