Askr documentation
UI & Components

Switch

Switch: anatomy, keyboard behavior, state, and theming in Askr.

Example

import { Field, FieldLabel, Switch } from '@askrjs/themes/components';

<Field>
  <Switch id="public" checked={isPublic()} onCheckedChange={setIsPublic} />
  <FieldLabel htmlFor="public">Public project</FieldLabel>
</Field>
Try Switch

Toggle a boolean setting.

Published props

Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.

SwitchAsChildProps

Import from @askrjs/themes/switch.

PropTypeDefaultDescription
asChildasChild: true;——
checkedchecked?: boolean | undefined;——
childrenchildren: JSXElement;——
defaultCheckeddefaultChecked?: boolean | undefined;——
disableddisabled?: boolean | undefined;——
namename?: string | undefined;——
onCheckedChangeonCheckedChange?: ((checked: boolean) => void) | undefined;——
refref?: Ref<Element>;——
requiredrequired?: boolean | undefined;——
typetype?: undefined;——
valuevalue?: string | undefined;——

SwitchButtonProps

Import from @askrjs/themes/switch.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
checkedchecked?: boolean | undefined;——
childrenchildren?: unknown;——
defaultCheckeddefaultChecked?: boolean | undefined;——
onCheckedChangeonCheckedChange?: ((checked: boolean) => void) | undefined;——
refref?: Ref<HTMLButtonElement>;——
requiredrequired?: boolean | undefined;——
typetype?: "button" | "submit" | "reset" | undefined;——

SwitchOwnProps

Import from @askrjs/themes/switch.

PropTypeDefaultDescription
checkedchecked?: boolean | undefined;——
childrenchildren?: unknown;——
defaultCheckeddefaultChecked?: boolean | undefined;——
disableddisabled?: boolean | undefined;——
namename?: string | undefined;——
onCheckedChangeonCheckedChange?: ((checked: boolean) => void) | undefined;——
requiredrequired?: boolean | undefined;——
valuevalue?: string | undefined;——

SwitchProps

Import from @askrjs/themes/switch.

PropTypeDefaultDescription
asChildasChild?: boolean | undefined;——
checkedchecked?: boolean | undefined;——
childrenchildren?: unknown;——
defaultCheckeddefaultChecked?: boolean | undefined;——
onCheckedChangeonCheckedChange?: ((checked: boolean) => void) | undefined;——
refref?: ((value: Element | null) => void) | { current: Element | null; } | ((value: HTMLButtonElement | null) => void) | { current: HTMLButtonElement | null; } | null | undefined;——
requiredrequired?: boolean | undefined;——
typetype?: "button" | "submit" | "reset" | undefined;——

Purpose

Switch is a binary on/off control that behaves like a checkbox but reads as a toggle — think settings screens where flipping a switch has an immediate effect rather than being one of several fields submitted together. Use it when the choice is genuinely binary and the change should feel instant, not when you need a labeled yes/no inside a longer form (Checkbox usually reads better there).

Install and import

Import the behavior from @askrjs/ui/switch and the default appearance from @askrjs/themes/switch. Both are real subpath exports of their respective packages, so you never need to reach into the root barrel just to render a switch.

Live examples

The demos cover a plain uncontrolled switch, a controlled one wired to checked/onCheckedChange, and a disabled state. Each renders as a native <button> under the hood, which is worth noticing if you're inspecting the DOM — there's no hidden checkbox unless you also pass name.

Anatomy

Switch has no sub-parts — it is a single control, unlike Accordion or Select. It renders as a button carrying role="switch" and aria-checked, and takes checked with onCheckedChange for controlled use or defaultChecked for uncontrolled. name and value make it submit inside a plain form, and required and disabled behave as they would on a native input. Pair it with Field from @askrjs/themes/field and FieldLabel from the @askrjs/themes/components barrel (it isn't exported from /field itself) rather than a bare label, so the description and error slots line up with the rest of your form controls.

State model

checked can be left uncontrolled via defaultChecked, or driven externally by passing checked alongside onCheckedChange. There's no separate internal boolean to fight with: whatever value you supply (or the current default) is exactly what renders, and every interaction routes through onCheckedChange before anything changes visually in a controlled setup.

Keyboard and accessibility

Switch exposes role="switch" and aria-checked. Test keyboard activation and checked-state changes, then verify that disabled controls suppress activation and preserve the intended tab order.

Styling and tokens

The track is styled through [data-slot="switch"] and data-state (checked/unchecked) rather than boolean class toggles, so a themed override can target [data-state="checked"] without needing to know which internal class name maps to which state. The thumb has no data-slot of its own — it's a ::after pseudo-element on the track, styled with [data-slot="switch"]::after, not a separate DOM node you can target directly. data-disabled is set on the track the same way for the disabled visual treatment.

API

Switch accepts checked, defaultChecked, onCheckedChange, disabled, required, name, and value — the last two exist specifically for the asChild=false case where the component ends up participating in native form submission. When asChild is true, Switch renders through Slot onto your own element instead of a <button>, and type is not accepted in that mode since there's no native button to type.

Edge cases

Per the package's FORM_INTEGRATION contract, Switch submits through a hidden checkbox-type input with value "on" when name is set — the same convention native checkboxes use, so existing form-handling code that expects checkbox-style submission keeps working. That hidden input renders unconditionally off name being set, independent of asChild — rendering via asChild onto a non-button element doesn't remove it. aria-disabled handling is likewise automatic either way: it comes from the same pressable() foundation call regardless of which branch renders, so you don't need to wire it yourself when using asChild. What asChild does remove is the native disabled attribute path specifically, since a non-button host has no such attribute to set.

If the option is one of several related boolean settings displayed together, compare against Checkbox, which typically reads better in dense lists. For a two-state control that toggles a pressed/unpressed visual rather than a settings value, see Toggle Family instead.