---
title: Switch
description: Toggle an on/off setting with switch semantics and form values.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# Switch

> Toggle an on/off setting with switch semantics and form values.

Use Switch for an on/off setting whose state is immediately meaningful. For an independent form choice that may be part of a set, use [Checkbox](https://moraine.subf.dev/components/checkbox.md).

## Basic usage

```tsx
import { Switch } from 'moraine'

export function Example() {
  return <Switch label="Notifications" />
}
```

## Anatomy

```text
Switch [component; slot=root]
├── input [internal]
├── track [slot]
│   └── thumb [slot]
│       └── icon [slot]
└── wrapper [slot]
    ├── label [slot]
    └── description [slot]
```

## Usage

### Checked state and forms

The focusable button exposes `role="switch"`; a hidden checkbox handles native form submission.
Use `checked` with `onCheckedChange` for controlled state, or `defaultChecked` for an initial value.

`trueValue` and `falseValue` map checked state to domain values for callbacks and schema-bound
forms. Native form submission uses the separate `value` prop only while checked; an unchecked
switch contributes no entry.

```tsx
import { Switch } from 'moraine'
import { createSignal } from 'solid-js'

export function StateForms() {
  const [enabled, setEnabled] = createSignal(true)

  return (
    <div class="max-w-md w-full space-y-3">
      <Switch
        checked={enabled()}
        onCheckedChange={setEnabled}
        label="Airplane mode"
        description="Disable all wireless connections."
      />
      <p class="text-xs text-muted-foreground">
        Status:{' '}
        <span class="text-foreground font-medium">{enabled() ? 'Enabled' : 'Disabled'}</span>
      </p>
    </div>
  )
}
```

### Loading ownership

`loading` is visual state supplied by the application. It does not represent an asynchronous operation on its own; decide whether the switch should also be disabled while that operation is pending.

```tsx
import { Switch } from 'moraine'
import { createSignal } from 'solid-js'

export function LoadingState() {
  const [loading, setLoading] = createSignal(false)
  const [checked, setChecked] = createSignal(false)

  const handleToggle = (next: boolean) => {
    setLoading(true)
    setTimeout(() => {
      setChecked(next)
      setLoading(false)
    }, 1000)
  }

  return (
    <div class="max-w-md w-full">
      <Switch
        checked={checked()}
        onCheckedChange={handleToggle}
        loading={loading()}
        label="Auto-deploy changes"
        description="Trigger production build on git push."
      />
    </div>
  )
}
```

## Examples

### Controlled state

```tsx
import { Switch } from 'moraine'
import { createSignal } from 'solid-js'

export function Controlled() {
  const [checked, setChecked] = createSignal(false)

  return (
    <div class="flex flex-col gap-3 max-w-xl">
      <Switch
        label="Email alerts"
        description="Uncontrolled"
        defaultChecked
        checkedIcon="i-lucide-bell"
        uncheckedIcon="i-lucide-bell-off"
      />
      <Switch
        label="Deploy protection"
        description={`Current: ${checked() ? 'enabled' : 'disabled'}`}
        checked={checked()}
        onCheckedChange={setChecked}
        checkedIcon="i-lucide-shield-check"
        uncheckedIcon="i-lucide-shield"
      />
    </div>
  )
}
```

### Custom true and false values

```tsx
import { Switch } from 'moraine'
import { createSignal } from 'solid-js'

export function CustomTrueFalseValues() {
  const [deploymentGuard, setDeploymentGuard] = createSignal<'enabled' | 'disabled'>('disabled')

  return (
    <div class="max-w-xl space-y-3">
      <Switch<'enabled', 'disabled'>
        label="Deployment gate"
        description="Domain value binding"
        trueValue="enabled"
        falseValue="disabled"
        checked={deploymentGuard()}
        onCheckedChange={setDeploymentGuard}
        checkedIcon="i-lucide-check-check"
        uncheckedIcon="i-lucide-x"
      />
      <p class="text-xs text-muted-foreground">Current value: {deploymentGuard()}</p>
    </div>
  )
}
```

### Loading icons

```tsx
import { Switch } from 'moraine'

export function LoadingIcons() {
  return (
    <div class="max-w-sm space-y-3">
      <Switch
        checked
        loading
        label="Sync preferences"
        description="The visual loading state is supplied by the application."
        checkedIcon="i-lucide:cloud-check"
        uncheckedIcon="i-lucide:cloud-off"
      />
      <Switch
        defaultChecked
        label="Use dark theme"
        checkedIcon="i-lucide:moon"
        uncheckedIcon="i-lucide:sun"
      />
    </div>
  )
}
```

### Form integration

```tsx
import { Button, createForm, Switch } from 'moraine'
import { createSignal } from 'solid-js'
import * as v from 'valibot'

export function FormIntegration() {
  const [submittedAutoRenew, setSubmittedAutoRenew] = createSignal(true)
  const form = createForm({
    schema: v.object({
      autoRenew: v.boolean(),
    }),
    initialInput: { autoRenew: true },
    validate: 'input',
  })

  return (
    <form.Form onSubmit={(output) => setSubmittedAutoRenew(output.autoRenew)}>
      <div class="max-w-xl space-y-4">
        <form.Field
          name="autoRenew"
          label="Subscription Settings"
          description="Automatically renew monthly billing at the end of the billing cycle."
        >
          <Switch label="Enable monthly auto-renewal" />
        </form.Field>
        <div class="flex gap-3 items-center">
          <Button type="submit" variant="secondary" size="sm">
            Save Preferences
          </Button>
          <p class="text-xs text-muted-foreground">
            Auto-renew: {submittedAutoRenew() ? 'Enabled' : 'Disabled'}
          </p>
        </div>
      </div>
    </form.Form>
  )
}
```

## Attributes

| Attributes | Slot | Description |
| --- | --- | --- |
| `data-checked` | `switch`, `switch-track`, `switch-thumb`, `switch-icon` | Present when the item or toggle is checked. |
| `data-disabled` | `switch`, `switch-track`, `switch-thumb` | Present when the component, slot, or item is disabled. |
| `data-invalid` | `switch`, `switch-track` | Present when the field or form has a validation error. |
| `data-loading` | `switch`, `switch-icon` | Present when the component or async operation is loading. |
| `data-readonly` | `switch`, `switch-track` | Present when the field is in read-only mode. |
| `data-required` | `switch`, `switch-label` | Present when the field input is required. |
| `data-unchecked` | `switch`, `switch-track`, `switch-thumb`, `switch-icon` | Present when the checkbox, radio item, or switch is unchecked. |

## Props

Props for the Switch component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| checked | TTrue \| TFalse \| undefined | — | Whether the switch is checked. |
| checkedIcon | IconT.Name \| undefined | — | Icon shown when the switch is checked. |
| defaultChecked | boolean \| undefined | — | Whether the switch is checked by default. |
| description | JSX.Element \| undefined | — | Description for the switch. |
| disabled | boolean \| undefined | false | Whether the input is disabled. |
| falseValue | TFalse \| undefined | false | Value to use when the switch is unchecked. |
| id | string \| undefined | — | The ID of the input element. |
| inputRef | Ref<HTMLInputElement> \| undefined | — | Optional inner input element ref. |
| label | JSX.Element \| undefined | — | Label for the switch. |
| loading | boolean \| undefined | false | Whether the switch is in a loading state. |
| loadingIcon | IconT.Name \| undefined | 'icon-loading' | Icon shown during loading state. |
| name | string \| undefined | — | The name of the input element, used for form submission. |
| onCheckedChange | ((value: TTrue \| TFalse) => void) \| undefined | — | Callback when the switch state changes. |
| onPointerDown | JSX.EventHandlerUnion<HTMLButtonElement, PointerEvent> \| undefined | — | Pointer down handler for the switch root container. |
| readOnly | boolean \| undefined | false | Whether the input is read-only. |
| required | boolean \| undefined | false | Whether the input is required. |
| size | 'sm' \| 'md' \| 'lg' \| undefined | 'md' | Visual size of the component. |
| trueValue | TTrue \| undefined | true | Value to use when the switch is checked. |
| uncheckedIcon | IconT.Name \| undefined | — | Icon shown when the switch is unchecked. |
| value | string \| undefined | 'on' | Native value submitted when the switch is checked. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |
