UI/UI
Switch
A control that allows the user to toggle between checked and not checked with smooth animations and multiple variants.
<Switch defaultChecked />
<Switch variant="secondary" defaultChecked />
Installation
Install following dependencies:
npm install @radix-ui/react-switch class-variance-authority motion
pnpm add @radix-ui/react-switch class-variance-authority motion
yarn add @radix-ui/react-switch class-variance-authority motion
bun add @radix-ui/react-switch class-variance-authority motion
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { motion } from "motion/react";
const switchVariants = cva(
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--hu-ring))] focus-visible:ring-offset-2 focus-visible:ring-offset-[hsl(var(--hu-background))] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[hsl(var(--hu-primary))] data-[state=unchecked]:bg-[hsl(var(--hu-input))]",
{
variants: {
variant: {
default:
"data-[state=checked]:bg-[hsl(var(--hu-primary))] data-[state=unchecked]:bg-[hsl(var(--hu-accent))]",
secondary:
"data-[state=checked]:bg-[hsl(var(--hu-secondary))] data-[state=unchecked]:bg-[hsl(var(--hu-accent))]",
},
size: {
sm: "h-5 w-9",
default: "h-6 w-11",
lg: "h-7 w-13",
xl: "h-8 w-15",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
const switchThumbVariants = cva(
"pointer-events-none block rounded-full bg-[hsl(var(--hu-background))] shadow-lg ring-0 transition-transform",
{
variants: {
variant: {
default: "bg-[hsl(var(--hu-background))]",
secondary: "bg-[hsl(var(--hu-background))]",
},
size: {
sm: "h-4 w-4 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
default:
"h-5 w-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
lg: "h-6 w-6 data-[state=checked]:translate-x-6 data-[state=unchecked]:translate-x-0",
xl: "h-7 w-7 data-[state=checked]:translate-x-7 data-[state=unchecked]:translate-x-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface SwitchProps
extends React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>,
VariantProps<typeof switchVariants> {
label?: string;
description?: string;
error?: string;
animated?: boolean;
}
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitive.Root>,
SwitchProps
>(
(
{
className,
variant,
size,
label,
description,
error,
animated = true,
id,
...props
},
ref,
) => {
const switchId = id || React.useId();
const switchElement = (
<SwitchPrimitive.Root
ref={ref}
id={switchId}
className={cn(switchVariants({ variant, size }), className)}
{...props}
>
<SwitchPrimitive.Thumb
className={cn(switchThumbVariants({ variant, size }))}
asChild={animated}
>
{animated ? (
<motion.div
layout
transition={{
type: "spring",
stiffness: 700,
damping: 30,
}}
className={cn(switchThumbVariants({ variant, size }))}
/>
) : (
<div className={cn(switchThumbVariants({ variant, size }))} />
)}
</SwitchPrimitive.Thumb>
</SwitchPrimitive.Root>
);
if (label || description || error) {
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
{switchElement}
<div className="grid gap-1.5 leading-none">
{label && (
<label
htmlFor={switchId}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
>
{label}
</label>
)}
{description && (
<p className="text-xs text-[hsl(var(--hu-muted-foreground))]">
{description}
</p>
)}
</div>
</div>
{error && (
<p className="text-xs text-[hsl(var(--hu-destructive))]">{error}</p>
)}
</div>
);
}
return switchElement;
},
);
Switch.displayName = SwitchPrimitive.Root.displayName;
export { Switch, switchVariants };
npx @hexta-ui/cli@latest add switch
pnpm dlx @hexta-ui/cli@latest add switch
yarn dlx @hexta-ui/cli@latest add switch
bun x @hexta-ui/cli@latest add switch
Usage
import { Switch } from "@/components/ui/Switch";
<Switch />
Examples
Default
<Switch />
Variants
<div className="space-y-4">
<Switch defaultChecked />
<Switch variant="secondary" defaultChecked />
</div>
Sizes
Small
Default
Large
Extra Large
<div className="space-y-4">
<Switch size="sm" defaultChecked />
<Switch defaultChecked />
<Switch size="lg" defaultChecked />
<Switch size="xl" defaultChecked />
</div>
With Label
Receive notifications on your device
<Switch
label="Push Notifications"
description="Receive notifications on your device"
/>
Disabled
<Switch disabled />
<Switch disabled defaultChecked />
Controlled
Settings
Get notified about important updates
Use dark theme across the application
Automatically save your work
Notifications: Disabled
Dark Mode: Enabled
Auto Save: Disabled
const [notifications, setNotifications] = React.useState(false);
const [darkMode, setDarkMode] = React.useState(true);
return (
<div className="space-y-4">
<Switch
label="Enable Notifications"
description="Get notified about important updates"
checked={notifications}
onCheckedChange={setNotifications}
/> <Switch
label="Dark Mode"
description="Use dark theme across the application"
variant="secondary"
checked={darkMode}
onCheckedChange={setDarkMode}
/>
</div>
);
With Error
Please read and accept our terms before proceeding
const [agreed, setAgreed] = React.useState(false);
<Switch
label="I agree to the terms and conditions"
description="Please read and accept our terms before proceeding"
checked={agreed}
onCheckedChange={setAgreed}
error={!agreed ? "You must agree to the terms" : undefined}
/>
Animation Control
Example switches:
<Switch defaultChecked animated={true} />
<Switch defaultChecked animated={false} />
Props
Prop | Type | Default |
---|---|---|
className? | string | undefined |
disabled? | boolean | false |
animated? | boolean | true |
error? | string | undefined |
description? | string | undefined |
label? | string | undefined |
onCheckedChange? | (checked: boolean) => void | undefined |
defaultChecked? | boolean | false |
checked? | boolean | undefined |
size? | "sm" | "default" | "lg" | "xl" | "default" |
variant? | "default" | "secondary" | "default" |
Edit on GitHub
Last updated on