Textarea
A multi-line text field built from slots and Context: pairs with a label and hint, grows to fit its content, and carries an optional character counter in its footer. Each part is a named export.
Installation#
Usage#
import {
TextareaRoot,
TextareaField,
TextareaLabel,
} from "@/components/ui/textarea"
export function Example() {
return (
<div className="flex flex-col gap-1.5">
<TextareaLabel htmlFor="message">Message</TextareaLabel>
<TextareaRoot>
<TextareaField id="message" placeholder="Write your message…" />
</TextareaRoot>
</div>
)
}Anatomy#
import {
TextareaRoot,
TextareaField,
TextareaFooter,
TextareaCount,
TextareaLabel,
TextareaHint,
} from "@/components/ui/textarea"
<TextareaLabel />
<TextareaRoot>
<TextareaField />
<TextareaFooter>
<TextareaCount current={0} />
</TextareaFooter>
</TextareaRoot>
<TextareaHint />Label & hint#
TextareaLabel sits above the control. Wire it with htmlFor pointing at the TextareaField’s id so clicking the label focuses the field. Pass required to append a destructive asterisk. TextareaHint renders helper text below; give it an id and reference it from the field’s aria-describedby. Both are the shared DS Label / Hint primitives.
Auto-resize#
Pass autoResize to TextareaField and the field grows to fit its content as you type, with no inner scrollbar. It rides the native CSS field-sizing: content where the browser supports it (no JavaScript at all), and falls back to a scrollHeight measurement everywhere else, so it works in every browser. Growth stays in sync on paste, autofill, or a controlled reset. It pins the drag handle to none, since manual dragging would fight the content height.
Character count addon#
For the common case, pass showCount to TextareaField and it renders the footer and counter for you, with no separate state and no extra parts. It tracks the field’s own length, so it works whether the field is controlled or uncontrolled. Add the native maxLength to show current / max and hard-cap the input.
Character counter (manual)#
For full control over the footer, compose it by hand. TextareaFooter is a slot inside the field’s border, ideal for a helper note on the left and a counter on the right. TextareaCount takes the current length and an optional max; it uses tabular-nums so the count never shifts the layout, and turns destructive once you go over. Pair it with hasError on the root to flush the whole field red.
Sizes#
Three sizes scale padding, text, and the minimum height: sm, md (the default outside a Form or compact density), and lg. Set size on TextareaRoot; every part picks it up from context.
Resize#
The resize prop on TextareaRoot controls the drag handle: vertical (default), none, horizontal, or both. The handle sits in the corner of the bordered box, level with the counter, inside the rounded edge.
Error#
Pass hasError to TextareaRoot to switch the border and focus ring to the destructive color and set aria-invalid on the underlying textarea. Pass the same hasError to TextareaHint so the helper text doubles as the error message.
Disabled#
API reference#
TextareaRoot
The bordered surface; shares size, error and disabled state with every part through context. Forwards all native <div> props; every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | Padding, text and minimum height. Unset, it follows a surrounding Form, then the ambient density (compact resolves to sm). |
| density | "comfortable" | "compact" | - | Picks the default size (compact to sm, comfortable to md); an explicit size wins. |
| resize | "none" | "vertical" | "horizontal" | "both" | "vertical" | The corner drag handle and the axis it resizes. autoResize on the field hides it. |
| hasError | boolean | false | Destructive border and focus ring, and aria-invalid on the field. Inherited from a surrounding Field when omitted. |
| disabled | boolean | false | Dims the root and disables the field. Inherited from a surrounding Field when omitted. |
| Data attribute | Description |
|---|---|
| data-disabled | Present when the textarea is disabled. |
| data-error | Present when the textarea is in the error state. |
| CSS variable | Description |
|---|---|
| --surface | Read for the field's opaque fill, so it matches the container; falls back to --background. |
TextareaField
The <textarea> itself. Inside a Field it adopts the field's id and aria-describedby. Forwards all native <textarea> props, including ref.
| Prop | Type | Default | Description |
|---|---|---|---|
| autoResize | boolean | false | Grows to fit the content (CSS field-sizing: content, with a scrollHeight fallback) and pins resize to none. |
| showCount | boolean | false | Renders a TextareaFooter with a TextareaCount below the field, controlled or uncontrolled. Add maxLength to show current / max. |
TextareaFooter
A row inside the border, below the field, for a note and a counter. Forwards all native <div> props.
TextareaCount
A tabular-nums character counter, announced politely. Forwards all native <span> props except children.
| Prop | Type | Default | Description |
|---|---|---|---|
| current* | number | - | The current character count. |
| max | number | - | Optional limit, rendered as current / max; the count turns destructive once exceeded. |
| Data attribute | Description |
|---|---|
| data-over | Present when current exceeds max. |
TextareaLabel
The shared Label primitive, placed above the root. Inside a Field it adopts the id, required and disabled. Forwards all native <label> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| required | boolean | false | Appends a destructive asterisk with an sr-only "(required)". |
| disabled | boolean | false | Dims the label. |
TextareaHint
The shared Hint primitive: helper or error text below the root. Inside a Field it adopts the hint id. Forwards all native <p> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| hasError | boolean | false | Switches to the destructive tone. Inherited from a surrounding Field when omitted. |