---
title: Switch
description: A two-state control that can be either on or off.
order: 30
tags: [components]
---

import { Preview } from "$lib/components/preview";
import { Switch } from "areia";

# Switch

A two-state control that can be either on or off. Areia's `Switch` uses `@data-slot/switch` for accessible, form-ready behavior when rendered with `Switch`.

[Source Code](https://github.com/ilhajs/areia/blob/main/packages/areia/src/components/switch/index.ts)

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch label="Enable notifications" />\n));'
  }
  lang="tsx"
>
  <Switch label="Enable notifications" />
</Preview>

## Import

```ts
import { Switch } from "areia";
```

## Usage

Call `Switch(...)` for interactive behavior. It initializes `@data-slot/switch`, syncs ARIA/state attributes, creates form inputs, and emits change events.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch\n    id="marketing-emails"\n    label="Marketing emails"\n    name="marketing-emails"\n    value="enabled"\n    uncheckedValue="disabled"\n  />\n));'
  }
  lang="tsx"
>
  <Switch
    id="marketing-emails"
    label="Marketing emails"
    name="marketing-emails"
    value="enabled"
    uncheckedValue="disabled"
  />
</Preview>

Use `Switch.Static(...)` for static markup or when you want to initialize behavior yourself. For most application usage, call `Switch(...)`.

### Ilha `bind:checked`

Inside an Ilha island (including nested child islands), you can bind island state without `onCheckedChange`:

```tsx
const Form = ilha
  .state("useBun", false)
  .render(({ state }) => (
    <Switch
      label="Use Bun"
      name="useBun"
      bind:checked={state.useBun}
    />
  ));
```

Requires **ilha** `^0.8.0` and a recent **areia** release. Controlled `checked` + `onCheckedChange` still works.

## Examples

### Off State

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => <Switch label="Dark mode" />);'
  }
  lang="tsx"
>
  <Switch label="Dark mode" />
</Preview>

### On State

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch label="Dark mode" checked />\n));'
  }
  lang="tsx"
>
  <Switch label="Dark mode" checked />
</Preview>

### Disabled

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <div class="flex flex-col gap-3">\n    <Switch label="Disabled off" disabled />\n    <Switch label="Disabled on" checked disabled />\n  </div>\n));'
  }
  lang="tsx"
>
  <div class="flex flex-col gap-3">
    <Switch label="Disabled off" disabled />
    <Switch label="Disabled on" checked disabled />
  </div>
</Preview>

### Read Only

Use `readOnly` to prevent user changes while keeping the field enabled for form submission.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch label="Managed by policy" checked readOnly />\n));'
  }
  lang="tsx"
>
  <Switch label="Managed by policy" checked readOnly />
</Preview>

### Variants

The Switch supports two variants: `default` for brand-colored toggles and `neutral` for monochrome toggles.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <div class="grid gap-3 sm:grid-cols-2">\n    <Switch label="Default off" variant="default" />\n    <Switch label="Default on" variant="default" checked />\n    <Switch label="Neutral off" variant="neutral" />\n    <Switch label="Neutral on" variant="neutral" checked />\n  </div>\n));'
  }
  lang="tsx"
>
  <div class="grid gap-3 sm:grid-cols-2">
    <Switch label="Default off" variant="default" />
    <Switch label="Default on" variant="default" checked />
    <Switch label="Neutral off" variant="neutral" />
    <Switch label="Neutral on" variant="neutral" checked />
  </div>
</Preview>

### Neutral Variant

The neutral variant is useful for subtle, lower-emphasis settings.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch label="Compact setting" variant="neutral" />\n));'
  }
  lang="tsx"
>
  <Switch label="Compact setting" variant="neutral" />
</Preview>

### Sizes

Three sizes are available: `sm`, `base`, and `lg`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <div class="flex flex-col gap-3">\n    <Switch label="Small" size="sm" />\n    <Switch label="Base" size="base" />\n    <Switch label="Large" size="lg" />\n  </div>\n));'
  }
  lang="tsx"
>
  <div class="flex flex-col gap-3">
    <Switch label="Small" size="sm" />
    <Switch label="Base" size="base" />
    <Switch label="Large" size="lg" />
  </div>
</Preview>

### Label First Layout

Use `controlFirst: false` to place the label before the switch.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch label="Enable beta features" controlFirst={false} />\n));'
  }
  lang="tsx"
>
  <Switch label="Enable beta features" controlFirst={false} />
</Preview>

### With Tooltip and Optional Indicator

Use `labelTooltip` for contextual help. Set `required: false` to show the optional indicator.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch\n    label="Usage analytics"\n    labelTooltip="Helps improve the product by sending anonymous usage data."\n    required={false}\n  />\n));'
  }
  lang="tsx"
>
  <Switch
    label="Usage analytics"
    labelTooltip="Helps improve the product by sending anonymous usage data."
    required={false}
  />
</Preview>

### Custom ID

When a custom `id` is provided, clicking the label toggles the switch.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch id="custom-switch-id" label="Custom id switch" />\n));'
  }
  lang="tsx"
>
  <Switch id="custom-switch-id" label="Custom id switch" />
</Preview>

### Without Visible Label

When a switch does not have visible label text, provide an accessible name with `aria-label`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch aria-label="Enable setting" />\n));'
  }
  lang="tsx"
>
  <Switch aria-label="Enable setting" />
</Preview>

### Switch Group

Group related switches with `Switch.Group`. It provides a shared legend, description, and error message for the group.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch.Group\n    legend="Notification settings"\n    description="Choose how you want to receive account updates."\n  >\n    <Switch.Item\n      label="Email notifications"\n      name="email"\n      value="enabled"\n    />\n    <Switch.Item\n      label="SMS notifications"\n      name="sms"\n      value="enabled"\n    />\n    <Switch.Item\n      label="Push notifications"\n      name="push"\n      value="enabled"\n    />\n  </Switch.Group>\n));'
  }
  lang="tsx"
>
  <Switch.Group
    legend="Notification settings"
    description="Choose how you want to receive account updates."
  >
    <Switch.Item
      label="Email notifications"
      name="email"
      value="enabled"
    />
    <Switch.Item
      label="SMS notifications"
      name="sms"
      value="enabled"
    />
    <Switch.Item
      label="Push notifications"
      name="push"
      value="enabled"
    />
  </Switch.Group>
</Preview>

### Group Label First

Set `controlFirst: false` on the group to place labels before all switches in the group.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch.Group legend="Privacy settings" controlFirst={false}>\n    <Switch.Item\n      label="Show profile publicly"\n      name="public-profile"\n    />\n    <Switch.Item\n      label="Allow search indexing"\n      name="search-indexing"\n    />\n  </Switch.Group>\n));'
  }
  lang="tsx"
>
  <Switch.Group legend="Privacy settings" controlFirst={false}>
    <Switch.Item
      label="Show profile publicly"
      name="public-profile"
    />
    <Switch.Item
      label="Allow search indexing"
      name="search-indexing"
    />
  </Switch.Group>
</Preview>

### Visually Hidden Legend

Use `Switch.Legend` with `class: "sr-only"` to keep the legend accessible to screen readers while hiding it visually.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch.Group>\n    <Switch.Legend class="sr-only">\n      Notification settings\n    </Switch.Legend>\n    <Switch.Item label="Email" name="email" />\n    <Switch.Item label="SMS" name="sms" />\n  </Switch.Group>\n));'
  }
  lang="tsx"
>
  <Switch.Group>
    <Switch.Legend class="sr-only">
      Notification settings
    </Switch.Legend>
    <Switch.Item label="Email" name="email" />
    <Switch.Item label="SMS" name="sms" />
  </Switch.Group>
</Preview>

### Custom Legend Styling

Use `Switch.Legend` directly when you need custom legend styling.

<Preview
  code={
    'import ilha from "ilha";\nimport { Switch } from "areia";\n\nexport default ilha.render(() => (\n  <Switch.Group>\n    <Switch.Legend class="text-sm font-semibold uppercase tracking-wide text-areia-subtle">\n      Notification settings\n    </Switch.Legend>\n    <Switch.Item\n      label="Product updates"\n      name="product-updates"\n    />\n    <Switch.Item\n      label="Security alerts"\n      name="security-alerts"\n      checked\n    />\n  </Switch.Group>\n));'
  }
  lang="tsx"
>
  <Switch.Group>
    <Switch.Legend class="text-sm font-semibold uppercase tracking-wide text-areia-subtle">
      Notification settings
    </Switch.Legend>
    <Switch.Item
      label="Product updates"
      name="product-updates"
    />
    <Switch.Item
      label="Security alerts"
      name="security-alerts"
      checked
    />
  </Switch.Group>
</Preview>

### Listening for Changes

`Switch` emits a bubbling `switch:change` event. You can also dispatch the inbound `switch:set` event supported by `@data-slot/switch`.

```ts
const switchRoot = document.querySelector(
  '[data-slot="switch"]',
);

switchRoot?.addEventListener("switch:change", (event) => {
  const { checked } = (event as CustomEvent).detail;
  console.log(checked);
});

switchRoot?.dispatchEvent(
  new CustomEvent("switch:set", { detail: { checked: true } }),
);
```

## API Reference

### Switch

Extends native `span` attributes for the switch control root.

| Prop              | Type                         | Default     | Description                                                  |
| ----------------- | ---------------------------- | ----------- | ------------------------------------------------------------ |
| `variant`         | `"default" \| "neutral"`     | `"default"` | Visual variant.                                              |
| `size`            | `"sm" \| "base" \| "lg"`     | `"base"`    | Switch size.                                                 |
| `label`           | `unknown`                    | -           | Label content. Optional for standalone visual switches.      |
| `labelTooltip`    | `string`                     | -           | Tooltip text displayed next to the label.                    |
| `required`        | `boolean`                    | -           | Native required state. `false` shows the optional indicator. |
| `controlFirst`    | `boolean`                    | `true`      | Places the switch before the label when true.                |
| `checked`         | `boolean`                    | -           | Initial checked state for `Switch`.                          |
| `defaultChecked`  | `boolean`                    | -           | Initial checked state.                                       |
| `disabled`        | `boolean`                    | -           | Disables interaction and form submission.                    |
| `readOnly`        | `boolean`                    | -           | Prevents interaction while keeping the field enabled.        |
| `transitioning`   | `boolean`                    | -           | Sets `aria-busy` for pending state.                          |
| `name`            | `string`                     | -           | Form field name.                                             |
| `value`           | `string`                     | `"on"`      | Submitted value when checked.                                |
| `uncheckedValue`  | `string`                     | -           | Submitted value when unchecked.                              |
| `onCheckedChange` | `(checked: boolean) => void` | -           | Called by `Switch` when checked state changes.               |
| `class`           | `string`                     | -           | Additional classes for the switch control.                   |
| `className`       | `string`                     | -           | Alias for `class`.                                           |

### Switch

`Switch` is an Ilha island. It renders the switch and initializes `@data-slot/switch` with the provided options.

It emits:

| Event           | Detail        | Description                       |
| --------------- | ------------- | --------------------------------- |
| `switch:change` | `{ checked }` | Fired when checked state changes. |

### Switch.Group

Extends native `fieldset` attributes.

| Prop           | Type      | Default | Description                                   |
| -------------- | --------- | ------- | --------------------------------------------- |
| `legend`       | `unknown` | -       | Legend content for the group.                 |
| `children`     | `unknown` | -       | Switch items and optional custom legend.      |
| `error`        | `unknown` | -       | Error message for the group.                  |
| `description`  | `unknown` | -       | Helper text for the group.                    |
| `disabled`     | `boolean` | -       | Disables all controls in the fieldset.        |
| `controlFirst` | `boolean` | `true`  | Controls label/control order for child items. |
| `class`        | `string`  | -       | Additional classes.                           |
| `className`    | `string`  | -       | Alias for `class`.                            |

### Subcomponents

| Component        | Description                                      |
| ---------------- | ------------------------------------------------ |
| `Switch.Item`    | Individual switch intended for use in a group.   |
| `Switch.Legend`  | Styled legend for switch groups.                 |
| `Switch.Control` | Bare switch control without field label wrapper. |

## Design Guidelines

### When to Use Switch

- Use switches for settings that take effect immediately.
- Use checkboxes for form choices that are submitted together later.
- Avoid switches for destructive or irreversible actions.

### Accessibility

- Provide `label`, `aria-label`, or `aria-labelledby` for every switch.
- Group related switches with `Switch.Group` and a legend.
- Use `description` to clarify when a setting takes effect.
- Disabled switches should not be the only way to explain unavailable settings; include supporting text where needed.
