Switch
A shadcn-style switch component built with Ark UI primitives.
export { default } from "./switch-basic";
Installation
npx shadcn@latest add @ark-cn/switchInstall the dependency required by this primitive:
npm install @ark-ui/reactCopy the component source into your app:
TSXcomponents/ui/switch.tsx
"use client";
import { Switch as SwitchPrimitive } from "@ark-ui/react/switch";
import { cn } from "@/lib/utils";
export type SwitchRootProps = SwitchPrimitive.RootProps;
export const SwitchRoot = ({
children,
className,
...props
}: SwitchRootProps) => {
return (
<SwitchPrimitive.Root
data-slot="switch"
{...props}
className={cn("inline-flex items-center gap-2", className)}
>
{children}
<SwitchPrimitive.HiddenInput />
</SwitchPrimitive.Root>
);
};
export type SwitchControlProps = SwitchPrimitive.ControlProps;
export const SwitchControl = ({ className, ...props }: SwitchControlProps) => {
return (
<SwitchPrimitive.Control
data-slot="switch-control"
className={cn(
"peer relative inline-flex h-6 w-10 shrink-0 cursor-pointer items-center rounded-full border border-transparent bg-input/70 p-0.5 shadow-xs/5 transition-colors outline-none",
"after:absolute after:-inset-x-3 after:-inset-y-2",
"data-[state=checked]:bg-primary",
"focus-visible:ring-3 focus-visible:ring-ring/50",
"disabled:cursor-not-allowed disabled:opacity-50",
"data-invalid:ring-3 data-invalid:ring-destructive/20 data-invalid:data-[state=checked]:bg-primary dark:data-invalid:ring-destructive/40",
className,
)}
{...props}
>
<SwitchThumb />
</SwitchPrimitive.Control>
);
};
export type SwitchThumbProps = SwitchPrimitive.ThumbProps;
export const SwitchThumb = ({ className, ...props }: SwitchThumbProps) => (
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"pointer-events-none block size-5 rounded-full bg-background shadow-sm ring-1 ring-border/30 transition-transform",
"data-[state=checked]:translate-x-4 rtl:data-[state=checked]:-translate-x-4",
className,
)}
{...props}
/>
);
export type SwitchProps = SwitchControlProps;
export const Switch = (props: SwitchProps) => <SwitchControl {...props} />;
export type SwitchLabelProps = SwitchPrimitive.LabelProps;
export const SwitchLabel = ({ className, ...props }: SwitchLabelProps) => (
<SwitchPrimitive.Label
data-slot="switch-label"
className={cn(
"text-sm font-medium text-foreground select-none data-disabled:opacity-50 data-invalid:text-destructive",
className,
)}
{...props}
/>
);
export type SwitchHiddenInputProps = SwitchPrimitive.HiddenInputProps;
export const SwitchHiddenInput = (props: SwitchHiddenInputProps) => (
<SwitchPrimitive.HiddenInput data-slot="switch-hidden-input" {...props} />
);
export const SwitchContext = SwitchPrimitive.Context;
export const SwitchRootProvider = SwitchPrimitive.RootProvider;
export type { UseSwitchProps, UseSwitchReturn } from "@ark-ui/react/switch";
export { useSwitch, useSwitchContext } from "@ark-ui/react/switch";
Update import aliases to match your project setup.
Usage
import * as Switch from "@/components/ui/switch"Read exported parts in src/components/ui/switch.tsx and compose the primitive according to the Ark UI pattern for this component.
Examples
Basic
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
const SwitchBasicDemo = () => (
<SwitchRoot>
<Switch />
<SwitchLabel>Label</SwitchLabel>
</SwitchRoot>
);
export default SwitchBasicDemo;
Initial checked
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
const SwitchInitialCheckedDemo = () => (
<SwitchRoot defaultChecked>
<Switch />
<SwitchLabel>Label</SwitchLabel>
</SwitchRoot>
);
export default SwitchInitialCheckedDemo;
Disabled
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
const SwitchDisabledDemo = () => (
<SwitchRoot disabled>
<Switch />
<SwitchLabel>Label</SwitchLabel>
</SwitchRoot>
);
export default SwitchDisabledDemo;
Controlled
import { useState } from "react";
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
const SwitchControlledDemo = () => {
const [checked, setChecked] = useState(false);
return (
<SwitchRoot
checked={checked}
onCheckedChange={(e) => setChecked(e.checked)}
>
<Switch />
<SwitchLabel>Label</SwitchLabel>
</SwitchRoot>
);
};
export default SwitchControlledDemo;
Context
import {
Switch,
SwitchContext,
SwitchLabel,
SwitchRoot,
} from "@/components/ui/switch";
const SwitchContextDemo = () => (
<SwitchRoot>
<Switch />
<SwitchContext>
{(ctx) => (
<SwitchLabel>
Feature is {ctx.checked ? "enabled" : "disabled"}
</SwitchLabel>
)}
</SwitchContext>
</SwitchRoot>
);
export default SwitchContextDemo;
Root Provider
import { Button } from "@/components/ui/button";
import {
Switch,
SwitchLabel,
SwitchRootProvider,
useSwitch,
} from "@/components/ui/switch";
const SwitchRootProviderDemo = () => {
const api = useSwitch();
return (
<div className="flex flex-col items-start gap-3">
<Button onClick={() => api.toggleChecked()} size="sm" variant="outline">
Toggle
</Button>
<SwitchRootProvider
value={api}
className="inline-flex items-center gap-2"
>
<Switch />
<SwitchLabel>Label</SwitchLabel>
</SwitchRootProvider>
</div>
);
};
export default SwitchRootProviderDemo;
With Field
Additional Info
import { Field, FieldDescription, FieldError } from "@/components/ui/field";
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
const SwitchWithFieldDemo = () => (
<Field className="max-w-sm w-full grid justify-center items-center">
<SwitchRoot>
<Switch />
<SwitchLabel>Label</SwitchLabel>
</SwitchRoot>
<FieldDescription>Additional Info</FieldDescription>
<FieldError>Error Info</FieldError>
</Field>
);
export default SwitchWithFieldDemo;
Card style
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
const SwitchCardStyleDemo = () => (
<SwitchRoot
className={cn(
"w-full max-w-sm items-start justify-between gap-6 rounded-lg border border-border p-3",
"hover:bg-accent/40",
"has-data-[state=checked]:border-primary/50 has-data-[state=checked]:bg-accent/40",
)}
>
<div className="flex flex-col gap-1">
<SwitchLabel className="font-medium">Enable notifications</SwitchLabel>
<span className="text-muted-foreground text-xs">
You can enable or disable notifications at any time.
</span>
</div>
<Switch className="mt-0.5" />
</SwitchRoot>
);
export default SwitchCardStyleDemo;
Nested
import { Switch, SwitchLabel, SwitchRoot } from "@/components/ui/switch";
const SwitchNestedDemo = () => (
<div className="flex w-full max-w-sm flex-col gap-3 rounded-lg border border-border p-3">
<SwitchRoot className="justify-between">
<SwitchLabel className="font-medium">Notifications</SwitchLabel>
<Switch />
</SwitchRoot>
<div className="ms-3 border-border border-s ps-3">
<SwitchRoot className="justify-between">
<SwitchLabel className="text-muted-foreground text-sm">
Email updates
</SwitchLabel>
<Switch />
</SwitchRoot>
</div>
</div>
);
export default SwitchNestedDemo;
API reference
This component mirrors the upstream Ark UI primitive.
See the ARK UI documentation for the full API.
Accessibility
See the Ark UI documentation for clarification.