forms
Textarea
Multi-line form control with rows or autosize, optional character counter, label, hint, error, and attribute forwarding.
Examples
Basic textarea
Label, hint, and rows-based sizing.
Keep it short and specific.
<x-ui.textarea
name="bio"
label="Bio"
hint="Keep it short and specific."
placeholder="Tell us about your product"
rows="4"
class="max-w-lg"
/>
<div class="w-full max-w-lg">
<label for="bio-basic" class="mb-1.5 block text-sm font-medium text-fg">Bio</label>
<textarea
id="bio-basic"
name="bio"
rows="4"
placeholder="Tell us about your product"
aria-describedby="bio-basic-hint"
class="min-h-24 w-full resize-y rounded-control border border-border bg-surface-raised px-3 py-2 text-sm text-fg shadow-sm placeholder:text-fg-subtle focus:border-border-strong"
></textarea>
<p id="bio-basic-hint" class="mt-1.5 text-xs text-fg-muted">Keep it short and specific.</p>
</div>
Character counter
Live count / max linked through aria-describedby.
0 / 120
<x-ui.textarea
name="summary"
label="Summary"
maxlength="120"
rows="3"
class="max-w-lg"
/>
<div class="w-full max-w-lg" x-data="{ count: 0, max: 120 }">
<label for="summary-counter" class="mb-1.5 block text-sm font-medium text-fg">Summary</label>
<textarea
id="summary-counter"
name="summary"
rows="3"
maxlength="120"
aria-describedby="summary-counter-counter"
class="min-h-24 w-full resize-y rounded-control border border-border bg-surface-raised px-3 py-2 text-sm text-fg shadow-sm focus:border-border-strong"
x-on:input="count = $event.target.value.length"
></textarea>
<p id="summary-counter-counter" class="mt-1.5 text-right text-xs text-fg-subtle" aria-live="polite">
<span x-text="count">0</span> / 120
</p>
</div>
Validation error
Error text with aria-invalid.
Please add at least 20 characters.
<x-ui.textarea
name="notes"
label="Notes"
value="Too short"
error="Please add at least 20 characters."
rows="3"
class="max-w-lg"
/>
<div class="w-full max-w-lg">
<label for="notes-error" class="mb-1.5 block text-sm font-medium text-fg">Notes</label>
<textarea
id="notes-error"
name="notes"
rows="3"
aria-invalid="true"
aria-describedby="notes-error-error"
class="min-h-24 w-full resize-y rounded-control border border-danger bg-surface-raised px-3 py-2 text-sm text-fg shadow-sm"
>Too short</textarea>
<p id="notes-error-error" class="mt-1.5 text-xs text-danger">Please add at least 20 characters.</p>
</div>
Props API
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string|null | null | Visible label linked via for/id. |
| hint | string|null | null | Helper text referenced by aria-describedby. |
| error | string|null | null | Validation message. Sets aria-invalid and contributes to aria-describedby. |
| rows | int | 4 | Initial visible row count for the native textarea. |
| maxlength | int|null | null | When set, shows a live count / max counter linked via aria-describedby. |
| autosize | bool | false | Grow height to fit content via Alpine on input. |
Accessibility notes
Architected with WCAG 2.2 AA patterns in mind. These notes document the exact keyboard and ARIA behavior for this component.
- Label for, textarea id, hint, error, and optional counter ids are wired through aria-describedby.
- Error state sets aria-invalid="true" on the textarea.
- Character counter uses aria-live="polite" so assistive tech hears count updates without stealing focus.
- Pass-through attributes (name, required, disabled, readonly, placeholder, x-model, wire:model) land on the textarea.
- Keyboard: Tab focuses the control. Standard text editing keys apply.
Built by Praesepe
Architecture & custom software partner
Praesepe uses these exact UI primitives to ship SME dashboards, inventory systems, invoicing desks, and internal tools faster, without rebuilding a design system for every client.
- Same Blade + Tailwind 4 + Alpine 3 stack you see in this docs site
- Opinionated for Indonesian SME ops: invoices, stock, roles, approvals
- Architected with WCAG 2.2 AA patterns in mind for keyboard and ARIA