Components · Forms
Switch
An on/off setting: a native checkbox with role=switch. No JavaScript.
0.39% of the 2,062 sites in The UI Index have a toggle (8 sites).
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ switch.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ switch.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS.
import type { InputHTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
import { switchClasses } from './switch.variants';
export type SwitchProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role'>;
/** An on/off setting that applies at once. A native checkbox with role="switch": no JavaScript needed. */
export function Switch({ className, ...props }: SwitchProps) {
return <input type="checkbox" role="switch" className={cn(switchClasses, className)} {...props} />;
}export const switchClasses =
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none items-center rounded-full border border-input bg-muted transition-colors before:absolute before:left-0.5 before:size-3.5 before:rounded-full before:bg-foreground/70 before:transition-[transform,width] before:content-[''] checked:border-primary checked:bg-primary checked:before:translate-x-4 checked:before:bg-primary-foreground enabled:not-checked:hover:border-foreground enabled:not-checked:hover:before:bg-foreground checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:hover:before:bg-primary-foreground enabled:active:before:w-4.5 checked:enabled:active:before:translate-x-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50";---
import type { HTMLAttributes } from 'astro/types';
import { cn } from '@/lib/utils';
import { switchClasses } from './switch.variants';
type Props = Omit<HTMLAttributes<'input'>, 'type' | 'role'>;
const { class: className, ...rest } = Astro.props;
---
<input type="checkbox" role="switch" class={cn(switchClasses, className)} {...rest} />export const switchClasses =
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none items-center rounded-full border border-input bg-muted transition-colors before:absolute before:left-0.5 before:size-3.5 before:rounded-full before:bg-foreground/70 before:transition-[transform,width] before:content-[''] checked:border-primary checked:bg-primary checked:before:translate-x-4 checked:before:bg-primary-foreground enabled:not-checked:hover:border-foreground enabled:not-checked:hover:before:bg-foreground checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:hover:before:bg-primary-foreground enabled:active:before:w-4.5 checked:enabled:active:before:translate-x-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50";<div class="grid gap-4">
<label class="flex items-center gap-2 text-sm leading-none font-medium has-[input]:items-start has-[input]:leading-snug has-[input]:[&>*:first-child]:mt-px select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50">
<input type="checkbox" role="switch" class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none items-center rounded-full border border-input bg-muted transition-colors before:absolute before:left-0.5 before:size-3.5 before:rounded-full before:bg-foreground/70 before:transition-[transform,width] before:content-[''] checked:border-primary checked:bg-primary checked:before:translate-x-4 checked:before:bg-primary-foreground enabled:not-checked:hover:border-foreground enabled:not-checked:hover:before:bg-foreground checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:hover:before:bg-primary-foreground enabled:active:before:w-4.5 checked:enabled:active:before:translate-x-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" checked>
Dark mode follows the system
</label>
<label class="flex items-center gap-2 text-sm leading-none font-medium has-[input]:items-start has-[input]:leading-snug has-[input]:[&>*:first-child]:mt-px select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50">
<input type="checkbox" role="switch" class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none items-center rounded-full border border-input bg-muted transition-colors before:absolute before:left-0.5 before:size-3.5 before:rounded-full before:bg-foreground/70 before:transition-[transform,width] before:content-[''] checked:border-primary checked:bg-primary checked:before:translate-x-4 checked:before:bg-primary-foreground enabled:not-checked:hover:border-foreground enabled:not-checked:hover:before:bg-foreground checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:hover:before:bg-primary-foreground enabled:active:before:w-4.5 checked:enabled:active:before:translate-x-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50">
Compact rows
</label>
<label class="flex items-center gap-2 text-sm leading-none font-medium has-[input]:items-start has-[input]:leading-snug has-[input]:[&>*:first-child]:mt-px select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 opacity-50">
<input type="checkbox" role="switch" class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none items-center rounded-full border border-input bg-muted transition-colors before:absolute before:left-0.5 before:size-3.5 before:rounded-full before:bg-foreground/70 before:transition-[transform,width] before:content-[''] checked:border-primary checked:bg-primary checked:before:translate-x-4 checked:before:bg-primary-foreground enabled:not-checked:hover:border-foreground enabled:not-checked:hover:before:bg-foreground checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:hover:before:bg-primary-foreground enabled:active:before:w-4.5 checked:enabled:active:before:translate-x-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" disabled>
Sync across devices (Pro)
</label>
</div>