Forms

Switch

A labeled on/off switch on the Field contract, with the label to its right: pass label, and checked with onCheckedChange (or defaultChecked). Clicking the label or pressing Space flips it; the thumb slides and stretches while pressed. With name, a switch that is on submits name=value with its form.

Sound an alarm when a step is done.

In Spoonjoy

Spoonjoy, a recipe app, is built with Ouro HX (how).

See it live, no sign-in needed: Test kitchen

In its source: app/components/recipe/IngredientInputToggle.tsx · app/components/notifications-section.tsx · app/routes/test-kitchen.tsx

Install

Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):

npx shadcn@latest init --base base --preset nova --yes --no-monorepo
npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13

Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):

npx shadcn@latest add ourostack/ouro-ui/switch#v1.2.13

Or, with the @ouro namespace set up:

npx shadcn@latest add @ouro/ouro-base @ouro/switch

Its tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):

npx shadcn@latest add ourostack/ouro-ui/switch#v1.2.13 ourostack/ouro-ui/switch-tests#v1.2.13
import { Switch } from "@/components/ouro/switch/switch";

Paths use shadcn's default @/ alias; the CLI rewrites them to yours.

Example

<Switch label="Wi-Fi" checked={wifi} onCheckedChange={setWifi} />
<Switch label="Nightly backups" name="backups" description="At 2 a.m." defaultChecked />

Usage

Use it for a setting that takes effect at once.

Use instead when

  • Use Checkbox instead for a choice that applies when a form is submitted, such as accepting terms.

Props

Type: SwitchProps.

checked
Type: boolean
Whether it is on, for a controlled switch; pair it with onCheckedChange.
className
Type: string
Layout only: margins, width, grid placement (mt-4, col-span-2). Appearance classes warn in development; style switches in your theme.
defaultChecked
Type: boolean
Whether it starts on, for an uncontrolled switch.
description
Type: ReactNode
Supporting text under the label, read after it through aria-describedby.
disabled
Type: boolean
Disables the switch and fades it.
error
Type: ReadonlyErrorMessages
What is wrong, from your server action or client check, shown under the label; the switch gets aria-invalid. A message is text or a React node (one with a link); an empty string or array means valid.
hideLabel
Type: boolean
Hides the label visually; screen readers still read it as the switch's name.
label (required)
Type: string
The setting it turns on, shown to the right of the switch and read as its name. Required: every control needs a name. To hide it visually, keep it and add hideLabel.
name
Type: string
The form field name it submits under when on.
onCheckedChange
Type: ((checked: boolean) => void)
Called with the new state when it turns on or off.
readOnly
Type: boolean
Shows the state but ignores clicks and keys, unlike disabled, which also fades it.
ref
Type: Ref<HTMLElement>
The track element, a <span role="switch">.
required
Type: boolean
The switch must be on to submit the form: it gets required and aria-required, and the label a marker.
value
Type: string
What it submits when on (on without one).

Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

  • defaultValue: Not a prop. A switch's starting state is defaultChecked: <Switch defaultChecked>.
  • errorMessage: Not a prop. Ouro UI uses error: <Switch error="Required.">.
  • helperText: Not a prop. Ouro UI uses description: <Switch description="...">.
  • invalid: Not a prop. error marks the switch invalid: <Switch error="What is wrong">.
  • isChecked: Not a prop. Ouro UI uses checked: <Switch checked={on}>.
  • isDisabled: Not a prop. Ouro UI uses disabled: <Switch disabled>.
  • isInvalid: Not a prop. error marks the switch invalid: <Switch error="What is wrong">.
  • isRequired: Not a prop. Ouro UI uses required: <Switch required>.
  • onChange: Not a prop. Ouro UI uses onCheckedChange, which gets the new state: onCheckedChange={setOn}.

className is for layout only

Pass margins, width and grid placement (mt-4, w-full, col-span-2).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.

Alignment classes (text-center) warn too: set alignment on a parent element.

Types

ErrorMessage

One error message: text, or a React element for a message that carries a link or other markup.

type ErrorMessage = string | ReactElement;

ReadonlyErrorMessages

A read-only message view accepted by fields and Form; mutable input arrays remain valid.

type ReadonlyErrorMessages = ErrorMessage | readonly ErrorMessage[];