Switch
An on/off toggle built on a real <input type="checkbox" role="switch"> — kept in the DOM
(visually hidden over a track + thumb) so keyboard, focus, and form submission work for free, and
assistive technology announces it as a switch. v-model is a boolean. The "on" track color and the
:focus-visible ring both come from the ori-color token; the track and thumb scale with
ori-font-size so size is a single prop.
The examples are organised by layer: the class reference is the standalone
@oriui/css layer, and the Framework API is the @oriui/vue component. Every
example is live — flip its code between HTML (the standalone classes, also your htmx / Astro / Svelte /
plain-HTML usage), Vue; HTML is the default.
Classes
A switch is a block class on the <label> wrapper plus two single-class token utilities — one class
per axis, no base class needed. The Vue props in Framework API map 1:1 to these.
| Class | Type | Description |
|---|---|---|
ori-switch | Block | Required base class — placed on the wrapping . |
ori-color_* | Color | primary · secondary · success · warn · danger · info · surface |
ori-font-size_* | Size | xs · sm · md · lg · xl · xxl — the track and thumb scale with this. |
ori-switch__input | Part | The visually-hidden native . |
ori-switch__track | Part | The visible pill track; aria-hidden. |
ori-switch__thumb | Part | The sliding circle inside the track; aria-hidden. |
ori-switch__label | Part | Optional visible text label beside the track. |
ori-switch_disabled | State | Added by the component when disabled is true; also sets the native disabled attribute on the input. |
Colors
Every semantic color role. The track fill and the focus ring both use --ori-color.
<label class="ori-switch ori-color_primary ori-font-size_md" for="s1">
<input id="s1" type="checkbox" role="switch" class="ori-switch__input" />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Primary</span>
</label>
<!-- swap the color class: ori-color_primary → ori-color_danger / ori-color_success / ori-color_warn / ori-color_info -->
Sizes
xs → xxl. A single ori-font-size_* class scales both the label text and the track/thumb geometry.
<!-- track and thumb scale off ori-font-size_* -->
<label class="ori-switch ori-color_primary ori-font-size_sm" for="sm">
<input id="sm" type="checkbox" role="switch" class="ori-switch__input" />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">sm</span>
</label>
<label class="ori-switch ori-color_primary ori-font-size_xl" for="xl">
<input id="xl" type="checkbox" role="switch" class="ori-switch__input" />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">xl</span>
</label>
States
disabled sets the native disabled on the <input> and adds ori-switch_disabled (reduced
opacity + not-allowed cursor) on the wrapper. invalid sets aria-invalid="true" on the input.
required sets the native required.
<label class="ori-switch ori-switch_disabled ori-color_primary ori-font-size_md" for="d1">
<input id="d1" type="checkbox" role="switch" class="ori-switch__input" disabled />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Disabled off</span>
</label>
<label class="ori-switch ori-switch_disabled ori-color_primary ori-font-size_md" for="d2">
<input id="d2" type="checkbox" role="switch" class="ori-switch__input" disabled checked />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Disabled on</span>
</label>
Invalid
invalid wires aria-invalid="true" so screen readers announce the error state.
<label class="ori-switch ori-color_danger ori-font-size_md" for="inv">
<input id="inv" type="checkbox" role="switch" class="ori-switch__input" aria-invalid="true" required />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Required field</span>
</label>
Without a label
Pass no label prop to render the track alone. Always pair a label-less switch with an external
aria-label or a visible text element connected via aria-labelledby.
<label class="ori-switch ori-color_primary ori-font-size_md" for="dm">
<input id="dm" type="checkbox" role="switch" class="ori-switch__input" aria-label="Dark mode" />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
</label>
Common patterns
A settings row — switch on the right aligned to a description — is the most common real-world use.
<div style="display: flex; flex-direction: column; gap: 0.75rem">
<label class="ori-switch ori-color_primary ori-font-size_md" for="p1">
<input id="p1" type="checkbox" role="switch" class="ori-switch__input" />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Email notifications</span>
</label>
<label class="ori-switch ori-color_secondary ori-font-size_md" for="p2">
<input id="p2" type="checkbox" role="switch" class="ori-switch__input" />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Dark mode</span>
</label>
<label class="ori-switch ori-switch_disabled ori-color_primary ori-font-size_md" for="p3">
<input id="p3" type="checkbox" role="switch" class="ori-switch__input" disabled />
<span class="ori-switch__track" aria-hidden="true"><span class="ori-switch__thumb"></span></span>
<span class="ori-switch__label">Auto-save</span>
</label>
</div>
Accessibility
The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and keyboard behaviour.
- Renders a real
<input type="checkbox" role="switch">so keyboard interaction, form submission, and screen-reader announcements work without any JavaScript bridges. - The wrapping
<label>is linked to the input byfor/id(auto-generated viauseId()unlessidis supplied) — clicking the label text or the track both toggle the switch. - The visual track and thumb are
aria-hidden="true"; the input is the only element in the accessibility tree. disabledis the real native attribute (not a class);invalidsetsaria-invalid="true".- A label-less switch must receive an accessible name via
aria-labeloraria-labelledbypassed as an attribute (it falls through to the<input>). :focus-visibleoutline appears on the track (using--ori-color) so the focus indicator is always visible without disrupting the unfocused appearance.
| Key | Action |
|---|---|
Space | Toggles the switch on/off. |
Tab | Moves focus to the switch. |
Framework API
The props, events, and slots of the Vue component. The standalone CSS layer has no component API — its surface is the classes above. (Svelte bindings are planned.)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
color | ThemeColor | 'primary' | Semantic color for the "on" track fill and the focus ring. |
disabled | boolean | false | Disables the control — sets native disabled and adds ori-switch_disabled on the wrapper. |
id | string | — | Overrides the auto-generated useId() id that links the <label> to the <input>. |
invalid | boolean | — | Sets aria-invalid="true" on the input; pair with a visible error message. |
label | string | — | Visible text label rendered beside the track. Omit to render the track alone. |
required | boolean | — | Sets the native required attribute on the input. |
size | ActionSize | 'md' | Controls both label text size and track/thumb geometry (xs · sm · md · lg · xl · xxl). |
Events & attributes
OriSwitch uses defineModel<boolean>() for two-way binding — bind with v-model to read the
checked state and emit update:modelValue when it toggles.
<OriSwitch v-model="enabled" label="Notifications" />
Because inheritAttrs: false is set and v-bind="$attrs" is forwarded to the <input>, all
native input attributes and listeners fall through to the real control:
@change,@focus,@blurland on the<input>.name,form,aria-describedby,aria-label,data-*pass through transparently.
Slots
| Slot | Falls back to | Description |
|---|---|---|
default | label prop | Visible text label rendered inside the native <label>, beside the track. Omit both to render the track alone. |