Textarea
A labelled, accessible multiline 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). Unlike a single-line input the field has no
fixed height — it grows from a rows-based min-height and stays user-resizable.
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 textarea is a block wrapper plus single-class token utilities — one class repoints one token, no
base class 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.
| Class | Type | Description |
|---|---|---|
ori-textarea | Block | Required base class (wrapper div). |
ori-textarea_* | Style | outline · fill |
ori-color_* | Color | primary · secondary · success · warn · danger · info · surface · background (focus ring accent) |
ori-textarea_* (size) | Size | xs · sm · md · lg · xl · xxl (sets min-height on wrapper via --ori-size-action) |
ori-size-radius_* | Radius | zero · xs · sm · md · lg · xl · rounded (field corners) |
ori-font-size_* | Font | xs · sm · md · lg · xl · xxl (label + field text scale) |
ori-textarea__label · ori-textarea__required · ori-textarea__field · ori-textarea__hint · ori-textarea__error | Part | label / required-asterisk / textarea / helper / error elements |
ori-textarea_fluid | Layout | full-width (stretches wrapper to 100 %) |
disabled · aria-invalid · aria-describedby | State | real attributes, not classes |
Variants
Two visual styles — outline (default, border visible at rest) and fill (tinted background, no
border).
<!-- default: bare ori-textarea is already outline + primary + md -->
<div class="ori-textarea ori-textarea_outline">
<label class="ori-textarea__label" for="t1">Outline</label>
<textarea id="t1" rows="3" placeholder="Type here..." class="ori-textarea__field"></textarea>
</div>
<!-- swap the variant: ori-textarea_outline → ori-textarea_fill -->
<div class="ori-textarea ori-textarea_fill">
<label class="ori-textarea__label" for="t1b">Fill</label>
<textarea id="t1b" rows="3" placeholder="Type here..." class="ori-textarea__field"></textarea>
</div>
Colors
The ori-color_* class controls the focus ring (and the danger color inherits for error text).
<div class="ori-textarea ori-color_danger">
<label class="ori-textarea__label" for="t2">danger</label>
<textarea id="t2" rows="3" class="ori-textarea__field"></textarea>
</div>
Sizes
xs → xxl. The size drives both the field min-height (via ori-textarea_* size sugar on the
wrapper) and the text scale (ori-font-size_*).
<!-- ori-textarea_xl sets min-height on the wrapper; ori-font-size_xl scales the text -->
<div class="ori-textarea ori-textarea_xl ori-font-size_xl">
<label class="ori-textarea__label" for="t3">xl</label>
<textarea id="t3" rows="3" class="ori-textarea__field"></textarea>
</div>
Radius
From zero (square) through the default md to rounded (heavily rounded corners).
<textarea class="ori-textarea__field ori-size-radius_zero"></textarea>
Rows
rows controls the initial visible height (and acts as the min-height). The field still grows as
the user types and remains manually resizable.
<textarea rows="2" class="ori-textarea__field …"></textarea>
<textarea rows="5" class="ori-textarea__field …"></textarea>
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).
Markdown is supported.
<div class="ori-textarea">
<label class="ori-textarea__label" for="bio">
Bio<span class="ori-textarea__required" aria-hidden="true">*</span>
</label>
<textarea
id="bio"
rows="3"
required
placeholder="Tell us about yourself..."
aria-describedby="bio-hint"
class="ori-textarea__field"
></textarea>
<p id="bio-hint" class="ori-textarea__hint">Markdown is supported.</p>
</div>
States
disabled uses the real attribute (also disables the resize handle). error renders a
role="alert" message and sets aria-invalid. invalid flips aria-invalid without a message
(for external validation).
Message must be at least 20 characters.
<!-- disabled -->
<textarea class="ori-textarea__field …" disabled></textarea>
<!-- invalid without message -->
<textarea class="ori-textarea__field …" aria-invalid="true"></textarea>
<!-- error: message gets role=alert and is referenced by aria-describedby -->
<textarea class="ori-textarea__field …" aria-invalid="true" aria-describedby="msg-error"></textarea>
<p id="msg-error" class="ori-textarea__error" role="alert">Message must be at least 20 characters.</p>
Fluid
fluid stretches the wrapper to the full width of its container.
<div class="ori-textarea ori-textarea_fluid …">…</div>
Common patterns
A real-world support form — label, required, hint, error, and fluid compose freely.
Attachments can be added on the next step.
<form style="display: flex; flex-direction: column; gap: 1rem; max-width: 32rem">
<div class="ori-textarea ori-textarea_fluid">
<label class="ori-textarea__label" for="sub">
Subject<span class="ori-textarea__required" aria-hidden="true">*</span>
</label>
<textarea
id="sub"
rows="3"
required
placeholder="Briefly describe the issue"
class="ori-textarea__field"
></textarea>
</div>
<div class="ori-textarea ori-textarea_fluid">
<label class="ori-textarea__label" for="msg">
Message<span class="ori-textarea__required" aria-hidden="true">*</span>
</label>
<textarea
id="msg"
rows="6"
required
placeholder="Provide as much detail as you can..."
aria-describedby="msg-hint"
class="ori-textarea__field"
></textarea>
<p id="msg-hint" class="ori-textarea__hint">Attachments can be added on the next step.</p>
</div>
</form>
Accessibility
The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and ARIA wiring.
labelis associated with the field viafor/id; the id is auto-generated (useId) when you don't pass one, so the association holds even without an explicitidprop.hintanderrorare wired througharia-describedby, referencing only the element that is actually rendered (errorsupersedeshint). Pass extra ids withdescribedbyto reference additional descriptions (e.g. a shared form note).errorsetsaria-invalid="true"and announces viarole="alert";invalidflipsaria-invalidon its own for external validation flows.- Uses the real
disabledattribute and nativerequired; the focus ring is always visible and switches to thedangercolor when invalid. - All native attributes (
name,autocomplete,maxlength,wrap,spellcheck, …) fall through to the underlying<textarea>— OriTextarea stays out of the way.
| Key | Action |
|---|---|
Tab | Moves 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
| Prop | Type | Default | Description |
|---|---|---|---|
color | ThemeColor | 'primary' | Accent color for focus ring: primary · secondary · success · warn · danger · info · surface · background. |
describedby | string | — | Extra element id(s) appended to aria-describedby (e.g. a shared form note). |
disabled | boolean | false | Real disabled attribute; blocks interaction, dims the field, and disables the resize handle. |
error | string | — | Error message rendered below the field (role="alert"); also sets aria-invalid="true". |
fluid | boolean | false | Full-width — stretches wrapper to 100 % of its container. |
hint | string | — | Helper text below the field; hidden while error is shown. |
id | string | — | Explicit id for the <textarea>; auto-generated via useId when omitted. |
invalid | boolean | false | Sets aria-invalid="true" without rendering an error message (for external validation). |
label | string | — | Visible <label> text, wired to the field via for/id. |
placeholder | string | — | Native placeholder text. |
radius | RadiusSize | 'md' | Corner radius of the field (zero · xs · sm · md · lg · xl · rounded). |
required | boolean | false | Native required attribute; also renders a visual asterisk (aria-hidden). |
rows | number | 3 | Visible rows of text — sets the field min-height; the field still grows and is resizable. |
size | ActionSize | 'md' | Field min-height + text scale (xs · sm · md · lg · xl · xxl). |
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:
<OriTextarea v-model="myValue" label="Message" />
OriTextarea sets inheritAttrs: false and spreads $attrs onto the underlying <textarea>, so
native attributes and listeners pass through directly to the field element — not the wrapper <div>:
- Attributes:
name,autocomplete,maxlength,minlength,wrap,spellcheck,readonly, … - Listeners:
@input,@change,@focus,@blur,@keydown, …
Slots
OriTextarea exposes no named slots.
| Slot | Description |
|---|---|
| (none) | No slots are defined. |