Askr
UI & Components

Textarea

Use textarea with the current published Askr packages.

  • @askrjs/ui/textarea0.0.13
  • @askrjs/themes/textarea0.0.13

How to use textarea

Textarea should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep textarea labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Button, Field, FieldHint, FieldLabel, Input } from '@askrjs/themes/components';

<Field>
  <FieldLabel for="project-name">Project name</FieldLabel>
  <Input id="project-name" name="name" required />
  <FieldHint>Shown to everyone in the workspace.</FieldHint>
  <Button type="submit">Save project</Button>
</Field>

Purpose

Textarea mirrors Input's approach for multi-line text: it's a thin headless wrapper around the native <textarea> element, handling disabled and tabIndex consistently and supporting asChild for polymorphic rendering, with no value management or auto-resize logic baked in. Use it for any multi-line free text field — comments, descriptions, messages — where the browser's native resize handle and scrolling behavior are enough.

Install and import

Textarea needs @askrjs/ui and @askrjs/askr for its headless behavior, with @askrjs/themes as an optional add-on for the default look. Import it headless via `import { Textarea } from '@askrjs/ui/textarea'`, or grab the same component from `@askrjs/themes/textarea` when you want styling included. As with Input, the themes package re-exports the @askrjs/ui Textarea directly instead of wrapping it in its own styled component.

Live examples

A default textarea next to a disabled one shows the baseline styling and cursor state, and an aria-invalid example demonstrates the validation border swap. Try resizing one by its handle to confirm the default CSS leaves vertical resize enabled rather than locking the box to a fixed height.

Anatomy

Textarea renders a single native <textarea> and nothing around it — no counter, no auto-grow wrapper, no label. If you need a character counter or a label/hint layout, compose it with Field and Label rather than expecting Textarea to provide it. With asChild, its props merge onto the one child you supply instead of it rendering its own element.

State model

Textarea has no state of its own beyond the disabled and tabIndex props it exposes — value, defaultValue, and onInput are ordinary native <textarea> attributes passed straight through, so controlled versus uncontrolled is determined by which of those you use, exactly like a plain HTML textarea.

Keyboard and accessibility

Textarea inherits native keyboard behavior (character input, Tab to leave the field, Enter for a newline rather than submission) without any extra ARIA from the component. It supports the same labeling paths as Input — a <label for>, aria-label, or aria-labelledby — defaults to tabIndex 0, and moves to tabIndex -1 when disabled.

Styling and tokens

The themed textarea targets [data-slot="textarea"], setting a --_textarea-min-height of 5rem, border and focus-ring tokens shared with Input, and resize: vertical so users can grow the box but not shrink it below its minimum. Like Input, an aria-invalid="true" attribute switches the border color to the danger token automatically — there's no separate error-state prop to set.

API

TextareaElementProps extends native <textarea> attributes (minus children and ref) with disabled?: boolean, tabIndex?: number, asChild?: false, and a ref typed to HTMLTextAreaElement; TextareaAsChildProps swaps those for asChild: true and a single required child. There's no Textarea-specific prop beyond what the native element and disabled/tabIndex already cover — everything else is standard HTML textarea behavior (rows, cols, maxLength, and so on).

Edge cases

Because the min-height is set in CSS rather than the rows attribute, a Textarea with very little content still reserves 5rem of vertical space by default — set rows or override the CSS variable if you need a shorter starting height. As with Input's asChild, wrapping a non-textarea-like element drops the forwarded props with no runtime warning, so keep asChild targets limited to elements that actually accept text input props.

See Input for the single-line equivalent and the same asChild pattern, and Form, Label, Field, and Input Group for pairing Textarea with labels and validation messaging.