oriUI

Checkbox

A styled, accessible checkbox built on a real <input type="checkbox"> — kept in the DOM (visually hidden over a custom box) so keyboard, focus, and native form submission work for free. The accent color and :focus-visible ring are driven by the ori-color token; invalid sets aria-invalid; disabled is the native attribute.

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 checkbox is a block class on the <label> wrapper plus single-class token utilities — one class repoints one token; no paired base class is needed. The Vue props in Framework API map 1:1 to these.

ClassTypeDescription
ori-checkboxBlockRequired base class — applied to the label wrapper.
ori-color_*Colorprimary · secondary · success · warn · danger · info · surface · background
ori-font-size_*Sizexs · sm · md · lg · xl · xxl · text (box and label scale together off the font size)
ori-checkbox__inputPartthe visually-hidden native input type=checkbox
ori-checkbox__boxPartthe visible styled square; aria-hidden
ori-checkbox__labelPartthe text label rendered next to the box
ori-checkbox_disabledStateadded by the component when disabled; dims the wrapper
disabled · aria-invalidStatereal attributes on the input, not classes

Colors

Every semantic color role. The accent fill and focus ring track ori-color.

html
<label class="ori-checkbox ori-color_primary" for="c1">
    <input id="c1" type="checkbox" class="ori-checkbox__input" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">Primary</span>
</label>
<!-- swap the color class: ori-color_primary → ori-color_success / ori-color_danger / ori-color_info / … -->
vue
<OriCheckbox v-model="checked" label="Primary" color="primary" />
<OriCheckbox v-model="checked" label="Success" color="success" />
<OriCheckbox v-model="checked" label="Danger" color="danger" />
<OriCheckbox v-model="checked" label="Info" color="info" />

Sizes

xsxxl. The box and label scale together because ori-font-size is set on the wrapper and the box dimensions use em units.

html
<!-- box and label scale together off ori-font-size_* -->
<label class="ori-checkbox ori-font-size_sm" for="sm1">
    <input id="sm1" type="checkbox" class="ori-checkbox__input" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">sm</span>
</label>
<label class="ori-checkbox ori-font-size_xl" for="xl1">
    <input id="xl1" type="checkbox" class="ori-checkbox__input" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">xl</span>
</label>
vue
<OriCheckbox v-model="checked" label="sm" size="sm" />
<OriCheckbox v-model="checked" label="md" size="md" />
<OriCheckbox v-model="checked" label="xl" size="xl" />

States

disabled is the native attribute; invalid sets aria-invalid="true" on the input.

html
<label class="ori-checkbox ori-checkbox_disabled" for="d1">
    <input id="d1" type="checkbox" class="ori-checkbox__input" disabled />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">Disabled</span>
</label>

<label class="ori-checkbox" for="inv1">
    <input id="inv1" type="checkbox" class="ori-checkbox__input" aria-invalid="true" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">Invalid</span>
</label>
vue
<!-- disabled — native attribute, dims wrapper via ori-checkbox_disabled -->
<OriCheckbox v-model="checked" label="Disabled" disabled />

<!-- invalid — sets aria-invalid="true" on the <input> -->
<OriCheckbox v-model="checked" label="Invalid" invalid />

<!-- required — sets required on the <input> -->
<OriCheckbox v-model="checked" label="Required" required />

Color × size

Color and size compose freely.

html
<label class="ori-checkbox ori-color_danger ori-font-size_lg" for="dl1">
    <input id="dl1" type="checkbox" class="ori-checkbox__input" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">Danger lg</span>
</label>
vue
<OriCheckbox v-model="checked" label="Danger lg" color="danger" size="lg" />
<OriCheckbox v-model="checked" label="Success sm" color="success" size="sm" />
<OriCheckbox v-model="checked" label="Info xl" color="info" size="xl" />

Checkbox group (array v-model)

Pass value on each checkbox and bind a shared array model. The native checkbox group mechanism handles checked state — no extra logic needed.

html
<!-- array binding is Vue-specific; in plain HTML use a name attribute for the group -->
<label class="ori-checkbox ori-color_primary" for="a">
    <input id="a" type="checkbox" class="ori-checkbox__input" name="opts" value="a" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">Option A</span>
</label>
<label class="ori-checkbox ori-color_primary" for="b">
    <input id="b" type="checkbox" class="ori-checkbox__input" name="opts" value="b" />
    <span class="ori-checkbox__box" aria-hidden="true"></span>
    <span class="ori-checkbox__label">Option B</span>
</label>
vue
<script setup>
import { ref } from 'vue';
const selected = ref([]);
</script>

<template>
    <OriCheckbox v-model="selected" label="Option A" value="a" />
    <OriCheckbox v-model="selected" label="Option B" value="b" />
    <OriCheckbox v-model="selected" label="Option C" value="c" />
    <p>Selected: {{ selected }}</p>
</template>

Common patterns

A real-world terms-and-conditions gate — required + invalid give the user feedback before submit.

html
<form>
    <label class="ori-checkbox ori-color_primary" for="terms">
        <input id="terms" type="checkbox" class="ori-checkbox__input" required />
        <span class="ori-checkbox__box" aria-hidden="true"></span>
        <span class="ori-checkbox__label">I agree to the terms and conditions</span>
    </label>
    <button type="submit">Continue</button>
</form>
vue
<script setup>
import { ref } from 'vue';
const agreed = ref(false);
const submitted = ref(false);

function submit() {
    submitted.value = true;
    if (agreed.value) {
        // proceed
    }
}
</script>

<template>
    <form @submit.prevent="submit">
        <OriCheckbox
            v-model="agreed"
            label="I agree to the terms and conditions"
            required
            :invalid="submitted && !agreed"
        />
        <button type="submit">Continue</button>
    </form>
</template>

Accessibility

The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and keyboard behaviour.

  • A real <input type="checkbox"> drives all keyboard and form behavior; the visual __box is aria-hidden="true".
  • The wrapping <label> has its for attribute linked to the input id (auto-generated via useId(), overridable with the id prop) — clicking anywhere on the label or its text toggles the checkbox.
  • Focus is visible via :focus-visible on the __box (2 px outline using var(--ori-color)); the ring never appears on mouse click.
  • disabled is the real native attribute — the input is excluded from tab order and form submission automatically; the wrapper dims via ori-checkbox_disabled.
  • invalid sets aria-invalid="true" on the input; pair it with an aria-describedby pointing at visible error text for a complete a11y contract.
KeyAction
TabMoves focus to / away from the checkbox.
SpaceToggles the checked state.

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

PropTypeDefaultDescription
colorThemeColor'primary'Accent color for the checked fill and focus ring.
disabledbooleanfalseNative disabled on the input; adds ori-checkbox_disabled (opacity) to the wrapper.
idstringOverrides the auto-generated useId() for the input/label pair.
invalidbooleanSets aria-invalid="true" on the input to signal a validation error.
labelstringVisible text rendered inside ori-checkbox__label. Omit when providing your own slot content.
requiredbooleanSets the native required attribute on the input.
sizeActionSize'md'Font (and therefore box) scale: xs · sm · md · lg · xl · xxl · text.
valuestring | numberBound to the array model for a native checkbox group; omit for a single boolean v-model.

Events & attributes

OriCheckbox sets inheritAttrs: false and forwards $attrs directly to the <input> element with v-bind="$attrs". This means native listeners and HTML attributes bind to the real input, not the outer <label>:

  • @change, @focus, @blur — fire on the <input>
  • name, form, autocomplete, aria-describedby — forwarded to the <input>

The two-way binding is handled by defineModel — use v-model for boolean (single) or array (group) state; the component emits update:modelValue internally.

Slots

SlotDescription
defaultRich label content, rendered in place of the label prop (which is the fallback). It stays inside the native <label>, so the for/id association holds — use it for an inline link or other markup.