A Radix toggle switch in two sizes with a CSS-animated thumb, for settings that take effect immediately. Stops animating under reduced motion.
import { Label } from "@/components/ballmac/label"
import { Switch } from "@/components/ballmac/switch"
export default function SwitchDemo() {
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<Switch id="airplane-mode" defaultChecked />
<Label htmlFor="airplane-mode">Airplane mode</Label>
</div>
<div className="flex items-center gap-3">
<Switch id="compact" size="sm" />
<Label htmlFor="compact">Compact view</Label>
</div>
</div>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/switchInstall the dependencies.
$ pnpm add radix-ui class-variance-authorityCopy the source into your project.
components/ballmac/switch.tsx// Ballmac UI: Switch. https://ui.ballmac.com/components/switch // Based on shadcn/ui's Switch (MIT, Copyright (c) 2023 shadcn), restyled with sizes. "use client" import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Switch as SwitchPrimitive } from "radix-ui" import { cn } from "@/lib/utils" const switchVariants = cva( "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs outline-none transition-[background-color,box-shadow] duration-200 motion-reduce:transition-none after:absolute after:-inset-2 after:content-[''] focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80", { variants: { size: { sm: "h-4 w-7", default: "h-5 w-9", }, }, defaultVariants: { size: "default" }, } ) type SwitchProps = React.ComponentProps<typeof SwitchPrimitive.Root> & VariantProps<typeof switchVariants> function Switch({ className, size = "default", ...props }: SwitchProps) { return ( <SwitchPrimitive.Root data-slot="switch" data-size={size ?? "default"} className={cn(switchVariants({ size }), className)} {...props} > <SwitchPrimitive.Thumb data-slot="switch-thumb" className={cn( "pointer-events-none block rounded-full bg-background shadow-[0_1px_2px_0_rgb(0_0_0/0.2)] ring-0 transition-transform duration-200 ease-[cubic-bezier(0.2,0.8,0.2,1)] motion-reduce:transition-none data-[state=unchecked]:translate-x-px data-[state=checked]:translate-x-[calc(100%+1px)] rtl:data-[state=unchecked]:-translate-x-px rtl:data-[state=checked]:-translate-x-[calc(100%+1px)] dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground", "size-4 group-data-[size=sm]/switch:size-3" )} /> </SwitchPrimitive.Root> ) } export { Switch, switchVariants, type SwitchProps }Update the import paths to match your project setup.
Usage
import { Switch } from "@/components/ballmac/switch"The full example is in the Code tab above.
Examples
Settings rows
A summary of activity every Monday.
Notify me when someone @mentions me.
Occasional emails about new features.
import { Label } from "@/components/ballmac/label"
import { Switch } from "@/components/ballmac/switch"
const settings = [
{ id: "email-digest", label: "Weekly digest", description: "A summary of activity every Monday.", on: true },
{ id: "mentions", label: "Mentions", description: "Notify me when someone @mentions me.", on: true },
{ id: "marketing", label: "Product news", description: "Occasional emails about new features.", on: false },
]
export default function SwitchSettings() {
return (
<div className="w-full max-w-md divide-y rounded-xl border bg-card">
{settings.map((s) => (
<div key={s.id} className="flex items-center justify-between gap-4 p-4">
<div className="grid gap-1">
<Label htmlFor={s.id}>{s.label}</Label>
<p id={`${s.id}-description`} className="text-sm text-muted-foreground">
{s.description}
</p>
</div>
<Switch id={s.id} defaultChecked={s.on} aria-describedby={`${s.id}-description`} />
</div>
))}
</div>
)
}API reference
| Prop | Type | Default |
|---|---|---|
size | "sm" | "default" | "default" |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Space | Toggles the switch |
| Enter | Toggles the switch |
| Tab | Moves focus to the switch |
Use with AI
On/off control for settings that apply immediately. Pair with a Label via id/htmlFor; use size="sm" in dense lists. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Switch (@ballmac/switch) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Enabling or disabling a feature or notification
- Settings pages with labelled rows
- Toggles inside dense menus or tables (size sm)
Not for
- Choices submitted later with a form, or agreeing to terms (use checkbox)
- More than two options (use select)
Registry JSON: https://ui.ballmac.com/r/switch.json
Credits
Based on shadcn/ui Switch, adapted by Ballmac. Free to use in personal and commercial projects.
- Registry
- shadcn/utils
Pairs well with
Label
An accessible form label built on Radix Label that dims itself when the paired peer control is disabled and never selects text on double-click.
Badge
A small pill label in four variants with an optional status dot and success, warning and error tones. Style links with badgeVariants().
Accordion
Vertically stacked disclosure sections on Radix Accordion, single or multiple open, with hairline dividers, a rotating plus and height animation.
Avatar
A Radix avatar with image and initials fallback, three sizes, an optional presence dot, and AvatarGroup for overlapping stacks with a +N overflow counter.