Skip to content

Switch

An on/off toggle built on the native <input type="checkbox">, carrying the shared root field composition.

Usage

<input is="root-switch" type="checkbox" data-label="Enable notifications" />

The composition comes from fieldDecorator, the same one Input, Checkbox and Radio use: a [field-wrapper] span draws the track and the knob, the native input stays in the DOM as [sr-only] - still the focus target and still what assistive tech reads - and data-label, data-hint, required and data-invalid behave as they do on Input.

<label>
  <span data-part="label">Enable notifications</span>
  <span field-wrapper>
    <input is="root-switch" type="checkbox" switch role="switch" sr-only />
  </span>
</label>

Switch semantics

The component sets the platform's switch attribute and role="switch" on itself, so the state is announced as on/off rather than checked/unchecked. checked stays the property and attribute that carries it, and form participation is the checkbox's own.

<input is="root-switch" type="checkbox" data-label="Dark mode" checked />

Indeterminate

The indeterminate attribute reflects onto the native .indeterminate property - announced as aria-checked="mixed" - and parks the thumb mid-travel on a resting track, so "not decided" cannot be read as on. A user toggle resolves it, clearing the attribute.

<input is="root-switch" type="checkbox" indeterminate data-label="Sync settings" />

States

Off is a Neutral 400 track with the thumb at the near end; on slides it to the far end and moves the track to Default 400. Hover and :active walk one and two ramp steps deeper on whichever of the two it is, and the whole label is the target. disabled leaves both ramps for Neutral 200.

<input is="root-switch" type="checkbox" data-label="Off" />
<input is="root-switch" type="checkbox" data-label="On" checked />
<input is="root-switch" type="checkbox" data-label="Focused" data-demo="focus" />
<input is="root-switch" type="checkbox" data-label="Focused, on" checked data-demo="focus" />
<input is="root-switch" type="checkbox" data-label="Disabled" disabled />
<input is="root-switch" type="checkbox" data-label="Disabled, on" checked disabled />

The middle pair wears the focus ring without being focused: data-demo="focus" is the docs' own marker, and Focus says what it draws.

Sizes

data-size takes small, medium and large; medium is the default and needs no value written. The track measures 43x24 / 36x20 / 29x16px and the thumb is its height less four, inset by two - so the travel is what is left: 19 / 16 / 13px. The label follows: the md type step at large and medium, sm at small.

<input is="root-switch" type="checkbox" data-size="large" data-label="Large" checked />
<input is="root-switch" type="checkbox" data-label="Medium" checked />
<input is="root-switch" type="checkbox" data-size="small" data-label="Small" checked />

Description

data-hint puts a description under the label, indented past the track so it sits under the text and reads as this control's rather than the next one's.

<input
  is="root-switch"
  type="checkbox"
  data-label="Weekly digest"
  data-hint="One message on Monday, with what changed."
  checked
/>

Label placement

The label sits after the track by default. data-label-position moves it to any of the four sides - inline-start, block-start, inline-end (the default), block-end - logically, so the sides follow the writing mode.

All four below, clockwise from the top, stacked in a <fieldset>: the group is the component's own (see above), and it lays its rows out in a column - which is what lets the two block placements be read as above and below rather than as two columns that happen to be taller than the rest of the row.

<fieldset>
  <input is="root-switch" type="checkbox" data-label="Above the track" data-label-position="block-start" />
  <input is="root-switch" type="checkbox" data-label="After the track" data-label-position="inline-end" />
  <input is="root-switch" type="checkbox" data-label="Below the track" data-label-position="block-end" />
  <input is="root-switch" type="checkbox" data-label="Before the track" data-label-position="inline-start" />
</fieldset>

Focus

The ring is the foundations', drawn around the wrapper: the input is [sr-only] but never hidden, so it is what takes focus and _focus.css is what draws it.

Focus only exists while something has it, so a page cannot show it by asking. Marked data-demo="focus" - the docs' own attribute, not a component one - the example below wears the same ring, from the same tokens, on the element the real one lands on:

<input is="root-switch" type="checkbox" data-label="Focused" data-demo="focus" />
<input is="root-switch" type="checkbox" data-label="Focused, on" checked data-demo="focus" />

data-invalid sets aria-invalid on the control; data-error replaces the description with the message while it is set.

<input is="root-switch" type="checkbox" required data-label="Accept the terms" data-invalid="true" />

Attributes

Attribute Type Default Description
type 'checkbox' 'checkbox' Input type
checked boolean false Toggle state
disabled boolean false Disables the switch
data-label string - Label text; the component builds the <label>
required boolean false Marks the field required and draws the asterisk
indeterminate boolean false Mixed state (thumb mid-travel + aria-checked="mixed"); cleared on toggle
data-label-position 'inline-start' | 'block-start' | 'inline-end' | 'block-end' 'inline-end' Which side the label text sits on
data-invalid 'true' - Marks the control invalid: sets aria-invalid, no message is rendered

Parts

Part Description
[field-wrapper] Draws the track and the knob
[data-part="label"] The label text, holding the required marker

Events

Namespaced as <component>.<event>, bubbling and cancelable; detail also carries the originating DOM event as originalEvent.

Event Detail Description
root-switch.input { value } Fired on input
root-switch.change { value } Fired on change
root-switch.focus { value } Fired on focus
root-switch.blur { value } Fired on blur
root-switch.invalid { value } Fired when the control fails constraint validation
root-switch.data-label.change { value, oldValue } Fired when the label text changes

Extends

HTMLInputElement - use with <input is="root-switch">.