Skip to main content
Gremorie

Switch

Immediate-effect on/off toggle built on Radix Switch, with `sm` and `default` size presets.

Overview

Switch is built on @radix-ui/react-switch. Use it when toggling the control changes state right away - notifications on / off, dark mode, feature flags. For state that takes effect only on form submission, use Checkbox instead.

Two sizes - sm (compact) and default - are exposed via the size prop. The thumb consumes the size via a group-data-[size] selector so it stays proportional in both presets.

Preview

Installation

bash npx gremorie@latest add rx-switch
bash pnpm dlx gremorie@latest add rx-switch
bash yarn dlx gremorie@latest add rx-switch
bash bunx --bun gremorie@latest add rx-switch

Usage

import { Label, Switch } from "@gremorie/rx-forms";

export function Example() {
  return (
    <div className="flex items-center gap-2">
      <Switch id="notifications" defaultChecked />
      <Label htmlFor="notifications">Push notifications</Label>
    </div>
  );
}

Angular edition planned for Phase 5h. Star the repo to track progress.

API

<Switch>

PropTypeDefaultDescription
checkedboolean-Controlled checked state.
defaultCheckedboolean-Uncontrolled initial state.
onCheckedChange(checked: boolean) => void-Fires when the user toggles the switch.
disabledbooleanfalseDisables interaction.
requiredbooleanfalseRequired for form submission.
namestring-Form field name.
valuestring"on"Form field value when checked.
size"sm" | "default""default"Footprint preset. sm is h-3.5 w-6; default is h-[1.15rem] w-8.

Forwards to SwitchPrimitive.Root. Renders an internal SwitchPrimitive.Thumb that translates on data-state=checked.

Composition

  1. Always pair with a <Label> via htmlFor matching the switch's id.
  2. Use a wrapper with flex items-center gap-2 (or justify-between for settings rows where the label sits on one side and the switch on the other).
  3. For form-bound boolean values that take effect on submit, prefer Checkbox instead.

Variations

Settings row

The canonical pattern for settings pages - label on the left, switch on the right, both vertically centered.

<div className="flex items-center justify-between rounded-md border p-4">
  <div className="grid gap-0.5">
    <Label htmlFor="dark-mode">Dark mode</Label>
    <p className="text-sm text-muted-foreground">
      Use a dark theme across the app.
    </p>
  </div>
  <Switch id="dark-mode" defaultChecked />
</div>

Compact sm size for toolbars

Use the smaller preset inside dense surfaces (sidebars, mini-toolbars, table cells).

<div className="flex items-center gap-2">
  <Switch id="autosave" size="sm" defaultChecked />
  <Label htmlFor="autosave">Autosave</Label>
</div>

Controlled with optimistic update

For settings that hit the network, flip the switch optimistically and roll back on error.

function NotificationToggle() {
  const [enabled, setEnabled] = React.useState(false);

  async function handleToggle(next: boolean) {
    setEnabled(next);
    try {
      await api.updateNotifications(next);
    } catch {
      setEnabled(!next);
      toast.error('Could not update notifications.');
    }
  }

  return (
    <div className="flex items-center gap-2">
      <Switch
        id="email-notifications"
        checked={enabled}
        onCheckedChange={handleToggle}
      />
      <Label htmlFor="email-notifications">Email notifications</Label>
    </div>
  );
}

Accessibility

  • ARIA: Radix renders role="switch" with aria-checked reflecting the state. Screen readers announce the control as "switch" plus the current state.
  • Keyboard: Space toggles the switch; Tab / Shift+Tab move focus.
  • Label association: clicking the <Label> toggles the switch via standard htmlFor semantics.
  • Disabled: disabled removes the switch from the tab order, dims the surface, and propagates fade to the linked label via peer-disabled.
  • Distinction from Checkbox: Switch is for immediate state changes ("now"); Checkbox is for state captured on form submit ("on submit"). This affects both semantics and user expectations.
  • Checkbox - form-submission counterpart
  • Toggle - press-button (text/icon affordance) for stateful actions
  • Label - the canonical companion
  • Form - wire Switch into react-hook-form

On this page