Overview
Get Started
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
return (
<Label>
<Switch />
Marketing emails
</Label>
);
}Installation
import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
import { cn } from "@/lib/cn";
export function Switch({
className,
size = "default",
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
size?: "sm" | "default" | "lg";
}) {
return (
<SwitchPrimitive.Root
className={cn(
"[--thumb-size:--spacing(4)]",
"inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 focus-visible:ring-3 focus-visible:ring-ring data-disabled:cursor-not-allowed data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50",
"aria-invalid:border aria-invalid:border-danger aria-invalid:ring-danger/30",
size === "sm" && "[--thumb-size:--spacing(3)]",
size === "lg" && "[--thumb-size:--spacing(5)]",
className
)}
data-slot="switch"
{...props}
>
<SwitchPrimitive.Thumb
className={cn(
"pointer-events-none block aspect-square h-full origin-left in-[[role=switch]:active,[data-slot=label]:active,[data-slot=field-label]:active]:not-data-disabled:scale-x-110 in-[[role=switch]:active,[data-slot=label]:active,[data-slot=field-label]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.1)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s] data-checked:origin-[var(--thumb-size)_50%] data-checked:translate-x-[calc(var(--thumb-size)-4px)]"
)}
data-slot="switch-thumb"
/>
</SwitchPrimitive.Root>
);
}
export { SwitchPrimitive };Usage
import { Switch } from "@/components/ui/switch";<Switch />Examples
Sizes
Three sizes are available.
- sm: Small size switch. (
--spacing(3)/ 12px) - default: Default size switch. (
--spacing(4)/ 16px) - lg: Large size switch. (
--spacing(5)/ 20px)
You can customize the size more by overriding the --thumb-size CSS variable. For example, you can set --thumb-size: 1.5rem to make the thumb size 24px.
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
return (
<div className="flex flex-col gap-5">
<Label>
<Switch size="sm" />
Small size
</Label>
<Label>
<Switch />
Default size
</Label>
<Label>
<Switch size="lg" />
Large size
</Label>
<Label>
<Switch className="[--thumb-size:--spacing(6)]" />
Custom size
</Label>
</div>
);
}Disabled
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
return (
<Label>
<Switch disabled />
Marketing emails
</Label>
);
}Invalid
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
return (
<Label>
<Switch aria-invalid />
Marketing emails
</Label>
);
}With description
By enabling marketing emails, you agree to receive emails.
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
return (
<div className="flex items-start gap-2">
<div className="flex flex-col gap-1">
<Label htmlFor="marketing-emails">Marketing emails</Label>
<p className="text-muted-foreground text-xs">
By enabling marketing emails, you agree to receive emails.
</p>
</div>
<Switch defaultChecked id="marketing-emails" />
</div>
);
}Card style
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
return (
<Label
className="flex items-start gap-6 rounded-lg border p-3 hover:bg-accent/50 has-data-checked:border-primary/48 has-data-checked:bg-accent/50"
htmlFor="enable-notifications"
>
<div className="flex flex-col gap-1">
<p>Enable notifications</p>
<p className="text-muted-foreground text-xs">
You can enable or disable notifications at any time.
</p>
</div>
<Switch className="[--thumb-size:--spacing(3.5)]" defaultChecked id="enable-notifications" />
</Label>
);
}Controlled
"use client";
import { useState } from "react";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function SwitchDemo() {
const [isEmailEnabled, setIsEmailEnabled] = useState(false);
return (
<Label className="flex flex-col items-start gap-2">
<Switch checked={isEmailEnabled} onCheckedChange={setIsEmailEnabled} />
Enable marketing emails: {isEmailEnabled ? "Yes" : "No"}
</Label>
);
}Form integration
For demonstrating invalid state, the default value is set to undefined. Without toggling the switch, the switch will be in an invalid state when the form is submitted.
"use client";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Switch } from "@/components/ui/switch";
const formSchema = z.object({
marketing: z.boolean(),
});
type FormValues = z.infer<typeof formSchema>;
export function SwitchDemo() {
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: { marketing: undefined },
});
const onSubmit = async (data: FormValues) => {
alert(`Form submitted with data: ${JSON.stringify(data, null, 2)}`);
};
return (
<form onSubmit={form.handleSubmit(onSubmit)} className="flex w-full max-w-sm flex-col gap-4">
<Controller
name="marketing"
control={form.control}
render={({
field: { name, value, onChange, ...fieldProps },
fieldState: { invalid, isTouched, isDirty, error },
}) => (
<Field name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>
<div className="flex w-full items-center justify-between">
<FieldLabel>Marketing Emails</FieldLabel>
<Switch checked={value} onCheckedChange={onChange} {...fieldProps} />
</div>
<FieldError match={!!error}>{error?.message}</FieldError>
</Field>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? "Submitting..." : "Submit"}
</Button>
</form>
);
}