Textarea
A textarea is Headless UI's Textarea rendered inside a styled frame. The
frame carries the border, background and focus ring; the native
<textarea> inside is transparent and uses the md control padding.
Inside a Field it receives the field's id, aria-labelledby,
aria-describedby, aria-invalid and disabled state without any ids on
your side.
import Textarea from '@zuilib/primitives/textarea'
Variants
outline is the default field: --input border on --background. ghost
has no border until it is hovered or focused, for inline editing.
States
invalid colours the border and focus ring --danger and sets
aria-invalid. unsaved tints the field --input-unsaved. disabled
dims the frame and blocks input.
Resize and actions
resize maps to the CSS resize property; vertical is the default.
buttonContent renders bottom-right inside the frame and the text is
padded clear of it.
In a Field
Field generates the control id and wires the Label, FieldDescription
and FieldError to the textarea. Its invalid becomes the textarea's
default invalid, so one prop on the field paints the border and sets
aria-invalid.
Props
TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> with:
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'outline' | 'ghost' | 'outline' | |
invalid | boolean | Field invalid | aria-invalid, data-invalid and the danger styles. Inherits the enclosing Field when unset |
resize | 'none' | 'vertical' | 'horizontal' | 'both' | 'vertical' | |
rows | number | 4 | Native attribute; sets the initial height |
fullWidth | boolean | false | w-full on the frame |
unsaved | boolean | false | Tints the field --input-unsaved and sets data-unsaved; pair with Label unsaved |
buttonContent | ReactNode | — | Rendered bottom-right inside the frame; the text is padded clear of it |
disabled | boolean | Field disabled | Leave unset to inherit from Field / Fieldset |
className | string | — | Merged last onto the frame (data-slot="textarea") |
textareaClassName | string | — | Merged last onto the native <textarea> |
Every other prop (value, onChange, placeholder, name, maxLength,
data-*, …) lands on the native <textarea>, which is also the ref
target. Mouse and pointer enter/leave handlers are attached to the native
element too, so currentTarget is the one you expect.
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="textarea"] | div | The frame. Carries data-unsaved and Headless UI's data-focus / data-hover / data-disabled / data-invalid |
[data-slot="textarea-control"] | textarea | The native textarea; mirrors the same state attributes |
[data-slot="textarea-actions"] | div | Wraps buttonContent, absolutely positioned bottom-right |
/* Consumer CSS: square fields */
[data-slot="textarea"],
[data-slot="textarea-control"] {
border-radius: 0;
}
Tokens
| Token | Used for |
|---|---|
--control-padding-x-md | Horizontal padding of the native textarea |
--input, --background | Outline border and fill |
--input-unsaved | unsaved tint |
--ring, --danger | Focus ring colour; invalid state |
--radius-md | Corner radius |
--duration-fast | Colour transition |
Accessibility
- A native
<textarea>; inside aFieldit gets the field id,aria-labelledbyfromLabel,aria-describedbyfromFieldDescription/FieldError, anddisabledfrom the field or an enclosingFieldset. invalidsetsaria-invalid="true"anddata-invalid. Outside aField, associate a label withhtmlForand describe errors witharia-describedby.- Focus shows a 1px ring hugging the border (
focus-within), coloured--dangerwhen invalid. buttonContentsits inside the frame but after the textarea in DOM order, so its buttons are the next tab stop.disabledrenders the native attribute; the frame dims to 50% and shows a not-allowed cursor.
Related
- Input: the single-line twin with the same frame.
- TextareaField: label, description, error and character count in one.
- Field, Label, FieldDescription, FieldError: the field parts.