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 FormItem 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/components/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
state colours the border and focus ring; error also marks the control
invalid. unsaved tints the field --input-dirty. 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 FormItem
FormItem generates the control id and wires the Label, Description
and Message to the textarea. Its invalid becomes the textarea's default
invalid, so one prop on the item paints the border and sets aria-invalid.
Props
TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> with:
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'outline' | 'ghost' | 'outline' | |
state | 'default' | 'error' | 'success' | 'default' | Border and ring colour; error also marks the control invalid. Exposed as data-state |
invalid | boolean | FormItem invalid | aria-invalid, data-invalid and the error styles. Inherits the enclosing FormItem 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-dirty and sets data-unsaved; pair with Label isDirty |
buttonContent | ReactNode | — | Rendered bottom-right inside the frame; the text is padded clear of it |
disabled | boolean | FormItem disabled | Leave unset to inherit from FormItem / 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-state, 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-dirty | unsaved tint |
--ring, --destructive, --success | Focus ring colour per state |
--radius-md | Corner radius |
--duration-fast | Colour transition |
Accessibility
- A native
<textarea>; inside aFormItemit gets the field id,aria-labelledbyfromLabel,aria-describedbyfromDescription/Message, anddisabledfrom the item or an enclosingFieldset. invalid(orstate="error") setsaria-invalid="true"anddata-invalid. Outside aFormItem, associate a label withhtmlForand describe errors witharia-describedby.- Focus shows a 1px ring hugging the border (
focus-within), coloured by the validation state. 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.
- RichTextArea: label, description, error and character count in one.
- FormItem, Label, Description, Message: the field parts.