OperantAI Workflow Design System
ComfortableCompact

Switch is a binary on/off toggle, built on @radix-ui/react-switch for keyboard and ARIA behavior. Use it for settings that take effect immediately (e.g. "enable notifications") — for a choice among several mutually-exclusive options, use RadioGroup or .ods-segmented instead.

Installation

HTML — already included if you loaded the 3 stylesheets — see the Installation guide.

React

npm install @operant/ui

@operant/ui is currently a monorepo workspace package, not yet published to the public npm registry.

Usage

HTML

<button class="ods-switch" role="switch" aria-checked="false" aria-label="Enable notifications">
  <span class="ods-switch-thumb"></span>
</button>

React

import { Switch } from "@operant/ui";

export default function Example() {
  return <Switch aria-label="Enable notifications" />;
}

Examples

Default (unchecked)

HTML

<button class="ods-switch" role="switch" aria-checked="false" aria-label="Enable notifications">
  <span class="ods-switch-thumb"></span>
</button>

React

<Switch aria-label="Enable notifications" />

Checked

[data-state="checked"] fills the track with --color-primary and translates .ods-switch-thumb 16px to the right.

HTML

<button class="ods-switch" role="switch" aria-checked="true" data-state="checked" aria-label="Enable notifications">
  <span class="ods-switch-thumb"></span>
</button>

React

<Switch defaultChecked aria-label="Enable notifications" />

Disabled

.ods-switch:disabled / .is-disabled drops opacity to 50% and removes pointer events.

HTML

<button class="ods-switch" role="switch" aria-checked="false" aria-label="Enable notifications" disabled>
  <span class="ods-switch-thumb"></span>
</button>

React

<Switch disabled aria-label="Enable notifications" />

With a label

SwitchField pairs the control with visible label text, reusing .ods-checkbox-label (a generic label+control row shared across checkbox/radio/switch, despite the name).

HTML

<label class="ods-checkbox-label" for="notif-switch">
  <button class="ods-switch" role="switch" aria-checked="false" id="notif-switch">
    <span class="ods-switch-thumb"></span>
  </button>
  Enable email notifications
</label>

React

import { SwitchField } from "@operant/ui";

<SwitchField label="Enable email notifications" />

API Reference

<Switch>

Wraps Radix SwitchPrimitive.Root + Thumb. Type is React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root> — i.e. all native Radix Switch props are accepted directly.

PropTypeDefaultDescription
checkedboolean—Controlled checked state.
defaultCheckedbooleanfalseUncontrolled initial state.
onCheckedChange(checked: boolean) => void—Called when the state changes.
disabledbooleanfalseDisables the control.
requiredbooleanfalseMarks the field required in a form.
namestring—Form field name.
classNamestring—Merged with .ods-switch.

<SwitchField>

Convenience wrapper pairing Switch with a label, wired via id/htmlFor (auto-generated with useId() if id is omitted).

PropTypeDefaultDescription
labelReactNode—Required. Label content rendered next to the control.
...restSwitchProps—All Switch props.

HTML-side classes: .ods-switch (34×20px pill track; [data-state="checked"] fills it with --color-primary), .ods-switch-thumb (16px circle, translates from 2px to 16px on check), .ods-checkbox-label (shared label+control row).

Accessibility

  • Radix renders role="switch" with aria-checked kept in sync automatically — screen readers announce it as a toggle, not a checkbox.
  • Fully keyboard operable: focusable via Tab, toggled via Space.
  • Focus is visible via :focus-visible → --shadow-focus; disabled switches are skipped in the tab order.
  • A Switch with no visible label (icon-only or bare) must carry an aria-label describing what it toggles — see the Default/Checked/Disabled examples above, all of which set one.
  • State changes are silent (no role="status" announcement) since the aria-checked change on the switch element itself is what assistive tech reports — no additional live region is needed for a simple toggle.