Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Switch

A control that allows the user to toggle between checked and not checked.

Playground

—label
—description
—size
—checked
—disabled
—aria-invalid
Usage
Import Switch from the design system and pair it with Field and FieldLabel for proper layout and labeling. The hit area extends invisibly past the 32×18px track, so the target stays comfortable on touch screens.
import { Switch } from "@workspace/ui/components/switch"
import { Field, FieldLabel } from "@workspace/ui/components/field"

<Field orientation="horizontal">
  <Switch id="airplane-mode" />
  <FieldLabel htmlFor="airplane-mode">Airplane Mode</FieldLabel>
</Field>
Sizes
The control comes in two sizes — default (32×18px) and sm (24×14px). Set size on Switch; the sm size suits dense layouts like table rows and toolbars.
<Field orientation="horizontal">
  <Switch id="switch-sm" size="sm" defaultChecked />
  <FieldLabel htmlFor="switch-sm">Small switch</FieldLabel>
</Field>

<Field orientation="horizontal">
  <Switch id="switch-default" defaultChecked />
  <FieldLabel htmlFor="switch-default">Default switch</FieldLabel>
</Field>
Description
Wrap the label in FieldContent and add a FieldDescription for helper text. Placing the switch after the content pushes it to the end of the row — the common settings-list layout.

Focus is shared across devices, and turns off when you leave the app.

<Field orientation="horizontal">
  <FieldContent>
    <FieldLabel htmlFor="share">Share across devices</FieldLabel>
    <FieldDescription>
      Focus is shared across devices, and turns off when you leave the app.
    </FieldDescription>
  </FieldContent>
  <Switch id="share" defaultChecked />
</Field>
Choice Cards
Wrap the whole Field in its FieldLabel to turn it into a clickable card — the label border highlights with primary while the switch is checked. Use FieldTitle for the heading inside the card.
<FieldGroup>
  <FieldLabel htmlFor="switch-2fa">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>Multi-factor authentication</FieldTitle>
        <FieldDescription>
          Require a second confirmation step when signing in.
        </FieldDescription>
      </FieldContent>
      <Switch id="switch-2fa" defaultChecked />
    </Field>
  </FieldLabel>
  <FieldLabel htmlFor="switch-backup">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>Automatic backups</FieldTitle>
        <FieldDescription>
          Back up your workspace every night at 2 AM.
        </FieldDescription>
      </FieldContent>
      <Switch id="switch-backup" />
    </Field>
  </FieldLabel>
</FieldGroup>
Controlled
Use defaultChecked for uncontrolled switches, or checked and onCheckedChange to control the state. Unlike Checkbox, the callback receives a plain boolean — there is no indeterminate state.
const [checked, setChecked] = React.useState(false)

<Field orientation="horizontal">
  <Switch
    id="notifications"
    checked={checked}
    onCheckedChange={setChecked}
  />
  <FieldLabel htmlFor="notifications">
    {checked ? "Notifications on" : "Notifications off"}
  </FieldLabel>
</Field>
Disabled
Use the disabled prop to prevent interaction and add data-disabled to the Field so the label dims with the track. The checked state is preserved, so a disabled switch can stay on.
<Field orientation="horizontal" data-disabled>
  <Switch id="bluetooth" disabled />
  <FieldLabel htmlFor="bluetooth">Bluetooth</FieldLabel>
</Field>
<Field orientation="horizontal" data-disabled>
  <Switch id="wifi" disabled defaultChecked />
  <FieldLabel htmlFor="wifi">Wi-Fi</FieldLabel>
</Field>
Invalid
Set aria-invalid on the switch and data-invalid on the field wrapper to show the invalid styles — a destructive border and ring on the track, and destructive text on the label.
<Field orientation="horizontal" data-invalid>
  <Switch id="terms" aria-invalid />
  <FieldLabel htmlFor="terms">Accept terms and conditions</FieldLabel>
</Field>
RTL
Switches need no extra classes in right-to-left layouts — the thumb uses rtl: variants to slide toward the logical end, so it travels left when checked and the label sits on the correct side.
<FieldGroup dir="rtl" className="gap-3">
  <Field orientation="horizontal">
    <Switch id="airplane-rtl" defaultChecked />
    <FieldLabel htmlFor="airplane-rtl">وضع الطيران</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <Switch id="notifications-rtl" />
    <FieldLabel htmlFor="notifications-rtl">تفعيل الإشعارات</FieldLabel>
  </Field>
</FieldGroup>
API Reference
Switch wraps the Radix Switch primitive and accepts all of its props — uncontrolled via defaultChecked or controlled via checked + onCheckedChange. Inside a form it submits value under name like a native checkbox. The size prop is the only local addition.
PropTypeDefault
size"sm" | "default""default"
checkedboolean—
defaultCheckedbooleanfalse
onCheckedChange(checked: boolean) => void—
disabledboolean—
requiredbooleanfalse
namestring—
valuestring"on"

Tokens used

8 design tokens consumed by the Switch component.

  • --primary

    Primary

    data-checked:bg-primary
  • --primary-foreground

    Primary Foreground

    dark:data-checked:bg-primary-foreground
  • --input

    Input

    data-unchecked:bg-inputdark:data-unchecked:bg-input/80
  • --background

    Background

    bg-background
  • --foreground

    Foreground

    dark:data-unchecked:bg-foreground
  • --ring

    Ring

    focus-visible:border-ringfocus-visible:ring-ring/50
  • --destructive

    Destructive

    aria-invalid:border-destructivearia-invalid:ring-destructive/20dark:aria-invalid:border-destructive/50dark:aria-invalid:ring-destructive/40
  • --spacing

    Spacing unit (4px)

    size-4size-3after:-inset-x-3after:-inset-y-2