Switch

An immediate on-or-off control for settings.

Use a switch for settings that apply as soon as they are turned on or off.

tsx
import { Switch } from "@/components/ui/switch"export function Example() {  return (    <label className="flex items-center gap-2.5 text-sm tracking-wide">      Airplane mode      <Switch id="airplane" />    </label>  )}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add switch

Usage

import { Switch } from "@/components/ui/switch"
<Switch />
<label className="flex items-center gap-2.5 text-sm tracking-wide">  Airplane mode  <Switch id="airplane" /></label>

Description

Pair the control with a title and short helper text, aligned on the trailing edge.

tsx
import { Switch } from "@/components/ui/switch"export function Example() {  return (    <label className="flex w-full items-start justify-between gap-4">      <span className="grid gap-1">        <span className="text-sm font-medium tracking-wide">          Watch theme tokens        </span>        <span className="text-sm text-muted-foreground">          Rebuild docs when CSS variables change under globals.css.        </span>      </span>      <Switch id="watch-tokens" className="mt-0.5" />    </label>  )}

Choice card

Wrap the row in a Card to make the full surface clickable. The unchecked card stays outlined; the checked card fills.

tsx
import * as React from "react"import { Card, CardContent } from "@/components/ui/card"import { Switch } from "@/components/ui/switch"import { cn } from "@/lib/utils"export function Example() {  const [docs, setDocs] = React.useState(true)  const [registry, setRegistry] = React.useState(false)  return (    <div className="grid w-full gap-3">      <label className="block w-full cursor-pointer">        <Card className={cn("py-0", !docs && "bg-transparent")}>          <CardContent className="flex items-start justify-between gap-4 py-3">            <span className="grid gap-1">              <span className="text-sm font-medium tracking-wide">                Include in docs build              </span>              <span className="text-sm text-muted-foreground">                Ships markdown, demos, and tokens with the registry export.              </span>            </span>            <Switch              id="card-docs"              checked={docs}              onCheckedChange={setDocs}              className="mt-0.5"            />          </CardContent>        </Card>      </label>      <label className="block w-full cursor-pointer">        <Card className={cn("py-0", !registry && "bg-transparent")}>          <CardContent className="flex items-start justify-between gap-4 py-3">            <span className="grid gap-1">              <span className="text-sm font-medium tracking-wide">                Sync local registry              </span>              <span className="text-sm text-muted-foreground">                Pull component updates when the registry changes.              </span>            </span>            <Switch              id="card-registry"              checked={registry}              onCheckedChange={setRegistry}              className="mt-0.5"            />          </CardContent>        </Card>      </label>    </div>  )}

Disabled

Set disabled to lock the control. Dim the label too so the whole row reads as inactive.

tsx
import { Switch } from "@/components/ui/switch"export function Example() {  return (    <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">      Publish package — unlock after review      <Switch id="publish-locked" disabled />    </label>  )}

Invalid

Mark the control with aria-invalid when validation fails, and use destructive text for the label.

tsx
import { Switch } from "@/components/ui/switch"export function Example() {  return (    <label className="flex items-center gap-2.5 text-sm font-medium tracking-wide text-destructive">      Accept the license      <Switch id="license" aria-invalid />    </label>  )}

Size

Use size="sm" for a tighter track. Default is the standard size.

tsx
import { Switch } from "@/components/ui/switch"export function Example() {  return (    <div className="flex w-44 flex-col gap-4">      <label className="flex w-full items-center justify-between gap-4 text-sm tracking-wide">        Small        <Switch id="size-sm" size="sm" defaultChecked />      </label>      <label className="flex w-full items-center justify-between gap-4 text-sm tracking-wide">        Default        <Switch id="size-default" defaultChecked />      </label>    </div>  )}

Radius

Use radius for square (none), standard (default), or pill-shaped (full) corners.

tsx
import { Switch } from "@/components/ui/switch"export function Example() {  return (    <div className="flex w-52 flex-col gap-4">      <label className="flex w-full items-center justify-between gap-4 text-sm tracking-wide">        None        <Switch id="radius-none" radius="none" defaultChecked />      </label>      <label className="flex w-full items-center justify-between gap-4 text-sm tracking-wide">        Usual corners        <Switch id="radius-default" defaultChecked />      </label>      <label className="flex w-full items-center justify-between gap-4 text-sm tracking-wide">        Full        <Switch id="radius-full" radius="full" defaultChecked />      </label>    </div>  )}

API Reference

Switch

PropTypeDefault
checkedboolean
defaultCheckedbooleanfalse
onCheckedChange(checked: boolean) => void
size"default" | "sm""default"
radius"none" | "default" | "full""default"
disabledbooleanfalse
aria-invalidboolean
idstring
classNamestring

Also accepts the other native button attributes.

<Switch checked={checked} onCheckedChange={setChecked} />