oriUI

Input

A labelled, accessible text field with v-model. State is expressed through real attributes — the native disabled, aria-invalid when there is an error, and an aria-describedby that points at the rendered hint or error. The label is wired to the field with for/id (auto-generated via useId when you don't pass one), and arbitrary native attributes (name, autocomplete, inputmode, …) fall through to the underlying <input>.

This page is laid out by layer. The live demos are the @oriui/css layer — their code defaults to HTML (the standalone classes, also your htmx / Astro / Svelte / plain-HTML usage), with Vue one tab away; the Framework API documents the @oriui/vue component. Input is pure CSS — there is no behaviour layer in between.

Classes

A field is a block wrapper with single-class token utilities — one class repoints one token; no separate base class is needed. The ori-color_* accent drives the focus ring; the idle border is a neutral, theme-aware blend. The Vue props in Framework API map 1:1 to these.

ClassTypeDescription
ori-inputBlockRequired base class (wrapper div).
ori-input_*Styleoutline · fill
ori-color_*Colorprimary · secondary · success · warn · danger · info · surface · background (focus ring accent)
ori-input_* (size)Sizexs · sm · md · lg · xl · xxl field height (size sugar on the wrapper)
ori-size-radius_*Radiuszero · xs · sm · md · lg · xl · rounded (field corners)
ori-font-size_*Fontxs · sm · md · lg · xl · xxl (label + field text scale)
ori-input__label · ori-input__required · ori-input__field · ori-input__hint · ori-input__errorPartlabel / required-asterisk / input / helper / error elements
ori-input_fluidLayoutfull-width (stretches wrapper to 100 %)
disabled · aria-invalid · aria-describedbyStatereal attributes, not classes

Variants

Two visual styles — outline (default, border visible at rest) and fill (tinted background, no border).

html
<div class="ori-input ori-color_primary ori-font-size_md ori-input_outline">
    <label class="ori-input__label" for="f1">Outline</label>
    <input id="f1" placeholder="Type here" class="ori-input__field ori-size-radius_md" />
</div>

<!-- swap the variant: ori-input_outline → ori-input_fill -->
vue
<OriInput v-model="value" label="Outline" placeholder="Type here" variant="outline" />
<OriInput v-model="value" label="Fill" placeholder="Type here" variant="fill" />

Colors

The ori-color_* class controls the focus ring (and the danger color inherits for error text).

html
<div class="ori-input ori-color_danger ori-font-size_md ori-input_outline">
    <label class="ori-input__label" for="f2">danger</label>
    <input id="f2" class="ori-input__field ori-size-radius_md" />
</div>
vue
<OriInput v-model="value" label="primary" color="primary" />
<OriInput v-model="value" label="danger" color="danger" />

Sizes

xsxxl. The size drives both the field height (ori-input_* size sugar on the wrapper) and the text scale (ori-font-size_*).

html
<!-- size sugar on the wrapper (ori-input_xl); the field inherits the height via --ori-size-action -->
<div class="ori-input ori-color_primary ori-font-size_xl ori-input_outline ori-input_xl">
    <label class="ori-input__label" for="f3">xl</label>
    <input id="f3" class="ori-input__field ori-size-radius_md" />
</div>

<!-- a bare ori-input wrapper is already a valid md field — no size class needed for the default -->
vue
<OriInput v-model="value" label="sm" size="sm" />
<OriInput v-model="value" label="xl" size="xl" />

Radius

From zero (square) through the default md to rounded (pill-shaped field).

html
<input class="ori-input__field ori-size-radius_zero" />
vue
<OriInput v-model="value" label="zero" radius="zero" />
<OriInput v-model="value" label="rounded" radius="rounded" />

Label, hint & required

label renders a <label> linked to the field. hint renders a helper line below. required adds the real HTML attribute and a visual asterisk (hidden from assistive tech).

We never share it.

html
<div class="ori-input ori-color_primary ori-font-size_md ori-input_outline">
    <label class="ori-input__label" for="email">
        Email<span class="ori-input__required" aria-hidden="true">*</span>
    </label>
    <input
        id="email"
        type="email"
        required
        placeholder="you@example.com"
        aria-describedby="email-hint"
        class="ori-input__field ori-size-radius_md"
    />
    <p id="email-hint" class="ori-input__hint">We never share it.</p>
</div>
vue
<OriInput v-model="email" type="email" label="Email" placeholder="you@example.com" hint="We never share it." required />

States

disabled uses the real attribute. error renders a role="alert" message and sets aria-invalid. invalid flips aria-invalid without a message (for external validation).

html
<!-- disabled -->
<input class="ori-input__field …" disabled />

<!-- invalid without message -->
<input class="ori-input__field …" aria-invalid="true" />

<!-- error: message gets role=alert and is referenced by aria-describedby -->
<input class="ori-input__field …" aria-invalid="true" aria-describedby="email-error" />
<p id="email-error" class="ori-input__error" role="alert">Enter a valid email address.</p>
vue
<OriInput v-model="value" label="Disabled" disabled />
<OriInput v-model="value" label="Invalid (no message)" invalid />
<OriInput v-model="email" label="With error" error="Enter a valid email address." />

Fluid

fluid stretches the wrapper to the full width of its container.

html
<div class="ori-input ori-input_fluid …">…</div>
vue
<OriInput v-model="value" label="Full width" fluid />

Input types

type maps directly to the native <input type=""> — use email, password, search, tel, url, number, etc.

html
<input type="password" class="ori-input__field …" /> <input type="search" class="ori-input__field …" />
vue
<OriInput v-model="password" type="password" label="Password" placeholder="Enter password" />
<OriInput v-model="query" type="search" label="Search" placeholder="Search…" />

Common patterns

A real-world sign-in form — label, type, hint, required, and error compose freely.

Use a mix of letters, numbers and symbols.

html
<form style="display: flex; flex-direction: column; gap: 1rem; max-width: 24rem">
    <div class="ori-input ori-color_primary ori-font-size_md ori-input_outline">
        <label class="ori-input__label" for="si-email">
            Email<span class="ori-input__required" aria-hidden="true">*</span>
        </label>
        <input
            id="si-email"
            type="email"
            required
            placeholder="you@example.com"
            class="ori-input__field ori-size-radius_md"
        />
    </div>
    <div class="ori-input ori-color_primary ori-font-size_md ori-input_outline">
        <label class="ori-input__label" for="si-pw">
            Password<span class="ori-input__required" aria-hidden="true">*</span>
        </label>
        <input
            id="si-pw"
            type="password"
            required
            placeholder="Min 8 characters"
            aria-describedby="si-pw-hint"
            class="ori-input__field ori-size-radius_md"
        />
        <p id="si-pw-hint" class="ori-input__hint">Use a mix of letters, numbers and symbols.</p>
    </div>
</form>
vue
<form style="display: flex; flex-direction: column; gap: 1rem; max-width: 24rem">
    <OriInput v-model="email" type="email" label="Email" placeholder="you@example.com" required />
    <OriInput
        v-model="password"
        type="password"
        label="Password"
        placeholder="Min 8 characters"
        hint="Use a mix of letters, numbers and symbols."
        required
    />
</form>

Accessibility

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

  • label is associated with the field via for/id; the id is auto-generated (useId) when you don't pass one, so the association holds even without an explicit id prop.
  • hint and error are wired through aria-describedby, referencing only the element that is actually rendered (error supersedes hint). Pass extra ids with describedby to reference additional descriptions (e.g. a shared form note).
  • error sets aria-invalid="true" and announces via role="alert"; invalid flips aria-invalid on its own for external validation flows.
  • Uses the real disabled attribute and native required; the focus ring is always visible and switches to the danger color when invalid.
  • All native attributes (name, autocomplete, inputmode, maxlength, …) fall through to the underlying <input> — OriInput stays out of the way.
KeyAction
TabMoves focus to / away from the field.

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 focus ring: primary · secondary · success · warn · danger · info · surface · background.
describedbystringExtra element id(s) appended to aria-describedby (e.g. a shared form note).
disabledbooleanfalseReal disabled attribute; blocks interaction and dims the field.
errorstringError message rendered below the field (role="alert"); also sets aria-invalid="true".
fluidbooleanfalseFull-width — stretches wrapper to 100 % of its container.
hintstringHelper text below the field; hidden while error is shown.
idstringExplicit id for the <input>; auto-generated via useId when omitted.
invalidbooleanfalseSets aria-invalid="true" without rendering an error message (for external validation).
labelstringVisible <label> text, wired to the field via for/id.
placeholderstringNative placeholder text.
radiusRadiusSize'md'Corner radius of the field (zero · xs · sm · md · lg · xl · rounded).
requiredbooleanfalseNative required attribute; also renders a visual asterisk (aria-hidden).
sizeActionSize'md'Field height + text scale (xs · sm · md · lg · xl · xxl).
typestring'text'Native input type (text, email, password, search, tel, url, number, …).
variant'fill' | 'outline''outline'Visual style: outline (border) or fill (tinted background).

Events & attributes

v-model binds to a string via defineModel<string>() — it reads the modelValue prop and emits update:modelValue on input, so you use it like any controlled field:

vue
<OriInput v-model="myValue" label="Name" />

OriInput sets inheritAttrs: false and spreads $attrs onto the underlying <input>, so native attributes and listeners pass through directly to the field element — not the wrapper <div>:

  • Attributes: name, autocomplete, inputmode, maxlength, minlength, pattern, spellcheck, readonly, …
  • Listeners: @input, @change, @focus, @blur, @keydown, …

Slots

OriInput exposes no named slots.

SlotDescription
(none)No slots are defined.