J3W1 / DESIGN SYSTEM
Switch
An on/off control that takes effect immediately; a button with role switch and aria-checked, drawn as a square-cornered track with a square thumb that fills with the primary colour when on.
stable · LIGHT DOM · 1.1.0
Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
<j3w1-switch id="example-switch-default"><button class="switch" type="button" role="switch" aria-checked="false" aria-label="Wrap long lines">
<span class="switch-track"><span class="switch-thumb"></span></span>
</button></j3w1-switch>with-labels
<j3w1-switch id="example-switch-with-labels"><div class="switch-row">
<span class="switch-name" id="example-switch-with-labels-sw-follow-name">Follow system theme</span>
<button class="switch" type="button" role="switch" aria-checked="false" aria-labelledby="example-switch-with-labels-sw-follow-name">
<span class="switch-track"><span class="switch-thumb"></span></span>
<span class="switch-state" aria-hidden="true"><span class="switch-state-off">Off</span><span class="switch-state-on">On</span></span>
</button>
</div></j3w1-switch>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@1.1.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/switch.css';
import '@j3w1/ui/register/switch';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@1.1.0 j3w1-ui copy switch --out ./vendor/j3w1/switchThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
checked | CustomEvent | Default switch checked. |
value | CustomEvent | Default switch value. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
checked | boolean | Immediate switch state; a named switch exposes a matching native hidden form value. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | The user toggled the switch. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Purpose
A setting that turns on or off the moment it is toggled: notifications, wrap lines, follow system theme. A choice that is only applied on submit is a checkbox. The switch is a button because it acts; it is not a form field.
Anatomy
A <button role="switch">; inside it the track, a 32×16px box with square
corners, and the thumb, a 10px square that sits at the start when off and
at the end when on. The with-labels variant adds a visible name before the
button and an “Off” / “On” word after the track.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | track 1px color.border.control on color.surface.input; thumb color.text.default at the start | thumb position |
| hover | track background → color.interaction.hover.bg; on track → color.action.primary.hover-bg | cursor: pointer; hover-capable pointers only |
| focus-visible | ring 1px dashed color.interaction.focus.ring at −2px on the track | the ring |
| checked | track fills color.action.primary.bg; thumb color.action.primary.text moves to the end; border stays color.border.control; the word reads “On” | aria-checked="true"; thumb position; the word |
| checked+focus-visible | the fill and a ring in color.interaction.focus.ring-container on the track | position and ring |
| disabled | track color.interaction.disabled.bg with 1px color.border.disabled; thumb and name color.text.disabled | disabled; cursor: not-allowed |
| checked+disabled | as disabled with the thumb at the end | disabled; aria-checked="true"; thumb position |
The thumb moves without animation by default; a host may transition
transform for at most 120ms under prefers-reduced-motion: no-preference. Precedence: disabled > checked > hover; focus-visible is
always drawn.
Keyboard
Space and Enter toggle. Nothing else is bound; there is no arrow-key movement between switches. Focus never moves as a result of toggling.
Accessibility
role="switch" on a native button gives the correct name, role and value
with the button’s own keyboard handling; only aria-checked is scripted.
The name states what is on (“Wrap long lines”), never the current value;
the “Off” / “On” word is aria-hidden because assistive technology reads
aria-checked. The state is also carried by thumb position and, in the
with-labels variant, the word. Contrast: thumb 8.65:1 on the off track,
9.28:1 on the on track; track edge 4.45:1; ring 4.69:1 off, 5.57:1 on.
Portability
Two nested rectangles, a border and a fill; the thumb position is layout, not a transform, so it survives hosts with no animation. Hosts with a native switch use it, square it, and map the two fills.
Non-examples
A pill-shaped track or a round thumb. A switch that only takes effect on submit. A checkbox input styled as a switch. Colour as the only difference between on and off. A green on colour. A label that reads “On” as the name. Disabling by opacity. A sliding animation longer than 120ms or one that runs under reduced motion.