@cf/switch
Switch
An on/off toggle for a setting that takes effect straight away, in default and sm sizes.
Base UI's Switch with the stock colours replaced. Track and thumb are built from foreground and background only, because the --input colours shadcn ships left a light thumb on a light track in dark mode.
The track carries an after: pseudo-element that pads the hit area by 12px sideways and 8px vertically, so the 32px switch is easier to hit without changing its size. In the notes menus the Switch is only a display: the menu row is the control, and the Switch is hidden from assistive tech and pointer events.
import { Switch } from "@/components/ui/switch";
<Switch aria-label="Confidential" checked={on} onCheckedChange={setOn} />Props
| Prop | Type | Default |
|---|---|---|
| size | sm | default | default |
Rules
The same rules ship in DESIGN.md and the MCP server, for the agents building with this.
- must
Give an interactive Switch an accessible name, through a
FieldLabelor anaria-label.name-the-switch
- must
When a whole row is the control (for example a
DropdownMenuCheckboxItem), render the Switch witharia-hidden,tabIndex={-1}andpointer-events-none, and hide the row's tick with[&_[data-slot=menu-item-indicator]]:hidden.Otherwise there are two focusable controls and two state indicators for one setting.
display-only-in-rows
- should
Use
size="sm"inside menus and other dense rows, anddefaultin forms.small-in-dense-rows
- should
Don't recolour the off state with
--input; if you tint the on state, change onlydata-checked:bg-*.Track and thumb use foreground and background so off and on stay distinguishable in both themes; the stock
--inputcolours left a light thumb on a light track in dark mode.keep-foreground-colours
Install
bunx shadcn@latest add @cf/switchNeeds the @cf registry in your components.json once. The theme and any sibling components come along automatically.
Source
switch.tsxShowHide
"use client";
import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
import { cn } from "cn";
// Track and thumb are built from foreground/background only, so off and on
// stay distinguishable in both themes. The stock colours leaned on --input,
// which left a light thumb on a light track in dark mode.
function Switch({
className,
size = "default",
...props
}: SwitchPrimitive.Root.Props & {
size?: "sm" | "default";
}) {
return (
<SwitchPrimitive.Root
className={cn(
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent outline-none transition-all after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=sm]:h-[14px] data-[size=default]:w-[32px] data-[size=sm]:w-[24px] data-disabled:cursor-not-allowed data-checked:bg-foreground data-unchecked:bg-foreground/25 data-disabled:opacity-50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className
)}
data-size={size}
data-slot="switch"
{...props}
>
<SwitchPrimitive.Thumb
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-unchecked:translate-x-0"
data-slot="switch-thumb"
/>
</SwitchPrimitive.Root>
);
}
export { Switch };