# Switch (/docs/switch)





## API Reference [#api-reference]

<BaseUIReference href="https://base-ui.com/react/components/switch" />

## Basic [#basic]

<Preview cssProperties="switchPlaygroundCssProperties">
  <SwitchExample />

  <Preview.Code>
    {`
          import { Switch, SwitchField, SwitchLabel } from "moduix";

          export function SwitchDemo() {
            return (
              <SwitchField>
                <Switch defaultChecked />
                <SwitchLabel>Enable notifications</SwitchLabel>
              </SwitchField>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSSProperties>
    {(context) => <SwitchCssPropertiesPanel {...context} />}
  </Preview.CSSProperties>

  <Preview.CSSPlayground>
    {(context) => <SwitchCssPlaygroundPanel {...context} />}
  </Preview.CSSPlayground>
</Preview>

## Anatomy [#anatomy]

`Switch` renders the interactive control and its internal thumb automatically. Use
`SwitchField` and `SwitchLabel` when the label wraps the control; use a native label with `id`
when the label is a sibling.

```text
SwitchField
|- Switch
|  `- internal thumb
`- SwitchLabel
```

```tsx
<SwitchField>
  <Switch defaultChecked />
  <SwitchLabel>Enable notifications</SwitchLabel>
</SwitchField>
```

| Part           | Role                                                                                                           |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `Switch`       | Root state machine and visible track. Controls `checked`, `defaultChecked`, form props, and `size`.            |
| internal thumb | Movable visual marker rendered by `Switch`. Customize content with `thumb` and styles with `classNames.thumb`. |
| `SwitchField`  | Optional wrapping label for the common inline field pattern.                                                   |
| `SwitchLabel`  | Optional text label slot styled to match the switch field.                                                     |

`Switch` does not use portal-like service layers such as `portal`, `positioner`, `backdrop`,
or `viewport`. In most cases, keep the internal thumb and style the root `Switch`,
`SwitchField`, and `SwitchLabel`.

## Composition [#composition]

Use `Switch` directly for the control. Pass `className` to style the track and `classNames.thumb`
to style the internal thumb. Pass `thumb` when the thumb needs custom content, such as an icon.

Use `nativeButton` with `render={<button />}` for sibling `label` patterns. Use the render
callback when a native button switch needs to live inside a wrapping label without invalid nested
button markup.

## Examples [#examples]

### Sizes [#sizes]

Use `size` to align the switch with different form densities.
Override `--switch-width-{size}`, `--switch-height-{size}`, and `--switch-thumb-size-{size}` when a specific size needs custom dimensions.

<Preview>
  <SwitchSizesExample />

  <Preview.Code>
    {`
          import { Switch, SwitchField, SwitchLabel } from "moduix";
          import styles from "./switch-demo.module.css";

          export function SwitchSizesDemo() {
            return (
              <div className={styles.stack}>
                <SwitchField>
                  <Switch size="xs" defaultChecked />
                  <SwitchLabel>Extra-small</SwitchLabel>
                </SwitchField>
                <SwitchField>
                  <Switch size="sm" defaultChecked />
                  <SwitchLabel>Small</SwitchLabel>
                </SwitchField>
                <SwitchField>
                  <Switch size="md" defaultChecked />
                  <SwitchLabel>Medium</SwitchLabel>
                </SwitchField>
                <SwitchField>
                  <Switch size="lg" defaultChecked />
                  <SwitchLabel>Large</SwitchLabel>
                </SwitchField>
                <SwitchField>
                  <Switch size="xl" defaultChecked />
                  <SwitchLabel>Extra-large</SwitchLabel>
                </SwitchField>
              </div>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .stack {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: var(--spacing-3);
          }
        `}
  </Preview.CSS>
</Preview>

### Disabled [#disabled]

Use `disabled` to prevent interaction while preserving the current setting state in the UI.

<Preview>
  <SwitchDisabledExample />

  <Preview.Code>
    {`
          import { Switch, SwitchField, SwitchLabel } from "moduix";
          import styles from "./switch-demo.module.css";

          export function DisabledSwitchDemo() {
            return (
              <div className={styles.stack}>
                <SwitchField>
                  <Switch disabled />
                  <SwitchLabel>Enable dark mode</SwitchLabel>
                </SwitchField>
                <SwitchField>
                  <Switch defaultChecked disabled />
                  <SwitchLabel>Keep me signed in</SwitchLabel>
                </SwitchField>
              </div>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .stack {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: var(--spacing-3);
          }
        `}
  </Preview.CSS>
</Preview>

### Controlled [#controlled]

Control `checked` from React state when the switch needs to coordinate with other UI.

<Preview>
  <ControlledSwitchExample />

  <Preview.Code>
    {`
          import { Switch, SwitchField, SwitchLabel } from "moduix";
          import { useState } from "react";
          import styles from "./switch-demo.module.css";

          export function ControlledSwitchDemo() {
            const [checked, setChecked] = useState(true);

            return (
              <div className={styles.stack}>
                <SwitchField>
                  <Switch checked={checked} onCheckedChange={setChecked} />
                  <SwitchLabel>{checked ? "On" : "Off"}</SwitchLabel>
                </SwitchField>
                <span className={styles.hint}>Current value: {String(checked)}</span>
              </div>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .stack {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: var(--spacing-3);
          }

          .hint {
            color: var(--color-muted-foreground);
            font-size: var(--text-xs);
            line-height: var(--line-height-text-xs);
          }
        `}
  </Preview.CSS>
</Preview>

### Custom Icon [#custom-icon]

Pass `thumb` to render any icon from your application or icon library inside the internal thumb.

<Preview>
  <CustomIconSwitchExample />

  <Preview.Code>
    {`
          import { Switch, SwitchField, SwitchLabel } from "moduix";
          import type { ComponentProps } from "react";
          import styles from "./switch-demo.module.css";

          function PowerIcon(props: ComponentProps<"svg">) {
            return (
              <svg viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false" {...props}>
                <path
                  d="M8 2.5V7M5.1 4.3A5 5 0 1 0 10.9 4.3"
                  stroke="currentColor"
                  strokeWidth="1.8"
                  strokeLinecap="round"
                />
              </svg>
            );
          }

          export function CustomIconSwitchDemo() {
            return (
              <SwitchField>
                <Switch
                  defaultChecked
                  thumb={<PowerIcon />}
                  classNames={{ thumb: styles.customIconThumb }}
                />
                <SwitchLabel>Use custom thumb icon</SwitchLabel>
              </SwitchField>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .customIconThumb {
            --switch-thumb-color-checked: var(--color-primary);
            --switch-thumb-color-unchecked: var(--color-muted-foreground);
            --switch-thumb-icon-size: 0.75rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Class Names [#class-names]

Pass `className` to the field, root, and label slots when styling with CSS Modules, Tailwind CSS, or CSS-in-JS.

<Preview>
  <SwitchClassNameExample />

  <Preview.Code>
    {`
          import { Switch, SwitchField, SwitchLabel } from "moduix";
          import styles from "./switch-demo.module.css";

          export function StyledSwitchDemo() {
            return (
              <SwitchField className={styles.customField}>
                <Switch className={styles.customSwitch} defaultChecked />
                <SwitchLabel className={styles.customLabel}>Styled with className</SwitchLabel>
              </SwitchField>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .customField {
            gap: var(--spacing-3);
          }

          .customSwitch {
            --switch-bg: color-mix(in oklab, var(--color-primary) 12%, transparent);
            --switch-bg-hover: color-mix(in oklab, var(--color-primary) 18%, transparent);
            --switch-bg-checked: var(--color-primary);
            --switch-border-color: color-mix(in oklab, var(--color-primary) 45%, var(--color-border));
          }

          .customLabel {
            color: var(--color-primary);
          }
        `}
  </Preview.CSS>
</Preview>

### Sibling Label [#sibling-label]

Use `nativeButton` with `render={<button />}` for the sibling `label` pattern with `htmlFor` and `id`.

<Preview>
  <SwitchSiblingLabelNativeButtonExample />

  <Preview.Code>
    {`
          import { Switch } from "moduix";
          import { useId } from "react";
          import styles from "./switch-demo.module.css";

          export function SiblingLabelSwitchDemo() {
            const id = useId();

            return (
              <div className={styles.siblingRow}>
                <Switch nativeButton render={<button />} id={id} defaultChecked />
                <label htmlFor={id} className={styles.label}>
                  Receive product updates
                </label>
              </div>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .siblingRow {
            display: flex;
            align-items: center;
            gap: var(--switch-gap, var(--spacing-2));
          }

          .label {
            color: var(--switch-label-color, var(--color-foreground));
            font-size: var(--switch-label-font-size, var(--text-sm));
            font-weight: var(--switch-label-font-weight, var(--weight-medium));
            line-height: var(--switch-label-line-height, var(--line-height-text-sm));
          }
        `}
  </Preview.CSS>
</Preview>

### Native Button Render Callback [#native-button-render-callback]

Use the render callback when a native button switch needs to be wrapped by a label without producing invalid nested button markup.

<Preview>
  <SwitchNativeButtonRenderCallbackExample />

  <Preview.Code>
    {`
          import { Switch } from "moduix";
          import styles from "./switch-demo.module.css";

          export function NativeButtonSwitchDemo() {
            return (
              <Switch
                defaultChecked
                nativeButton
                render={(buttonProps) => (
                  <label className={styles.siblingRow}>
                    <button {...buttonProps} />
                    <span className={styles.label}>Enable reminders</span>
                  </label>
                )}
              />
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .siblingRow {
            display: flex;
            align-items: center;
            gap: var(--switch-gap, var(--spacing-2));
          }

          .label {
            color: var(--switch-label-color, var(--color-foreground));
            font-size: var(--switch-label-font-size, var(--text-sm));
            font-weight: var(--switch-label-font-weight, var(--weight-medium));
            line-height: var(--switch-label-line-height, var(--line-height-text-sm));
          }
        `}
  </Preview.CSS>
</Preview>

### Form Integration [#form-integration]

Use `Field` when the switch participates in form labeling, validation, or submission.

<Preview>
  <SwitchFormIntegrationExample />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            Switch,
            SwitchLabel,
          } from "moduix";

          export function SwitchFormDemo() {
            return (
              <Field name="notifications">
                <FieldLabel>
                  <Switch defaultChecked />
                  <SwitchLabel>Notifications</SwitchLabel>
                </FieldLabel>
              </Field>
            );
          }
        `}
  </Preview.Code>
</Preview>
