Skip to main content

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.

Basic usage#

import { Switch } from 'moraine'

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

Playground#

Receive a notification when a deployment completes.
Props
Slots

Anatomy#

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.

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.

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#

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#

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#

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#

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
data-checkedSlot: switch, switch-track, switch-thumb, switch-iconDescription: Present when the item or toggle is checked.
data-disabledSlot: switch, switch-track, switch-thumbDescription: Present when the component, slot, or item is disabled.
data-invalidSlot: switch, switch-trackDescription: Present when the field or form has a validation error.
data-loadingSlot: switch, switch-iconDescription: Present when the component or async operation is loading.
data-readonlySlot: switch, switch-trackDescription: Present when the field is in read-only mode.
data-requiredSlot: switch, switch-labelDescription: Present when the field input is required.
data-uncheckedSlot: switch, switch-track, switch-thumb, switch-iconDescription: Present when the checkbox, radio item, or switch is unchecked.

Props#

Props for the Switch component. Renders a <div> element by default.

Prop