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 organized 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 · warning · danger · info · surface · background
ori-font-size_*Sizexs · sm · md · lg · xl · xxl · inherit (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
disabled · aria-invalidStatereal attributes on the input, not classes; a disabled input dims its wrapper

À la carte: the classes above ship in @oriui/css/components/checkbox.css. Import a foundation (@oriui/css/base.css or @oriui/css/tokens.css) first — the token utilities (ori-color_*, ori-size-radius_*, …) live there, not in the component file. The full bundle @oriui/css is the default and already carries both; see à-la-carte imports.

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

xs → xxl. 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" 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 — the native attribute, which also dims the wrapper -->
<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 behavior.

  • 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, and the wrapper dims from it (:has(:disabled)), so a <fieldset disabled> dims it too.
  • 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.

Props

PropTypeDefaultDescription
colorThemeColor'primary'Accent color for the checked fill and focus ring.
disabledbooleanfalseNative disabled on the input; the wrapper dims from it.
idstring—Overrides the auto-generated useId() for the input/label pair.
invalidboolean—Sets aria-invalid="true" on the input to signal a validation error.
labelstring—Visible text rendered inside ori-checkbox__label. Omit when providing your own slot content.
requiredboolean—Sets the native required attribute on the input.
sizeActionSize'md'Font (and therefore box) scale: xs · sm · md · lg · xl · xxl · inherit.
valuestring | number—Bound 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.