Skip to main content

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.

Loading example

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.

Loading example

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.

Loading example

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.

Loading example

Props

TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> with:

PropTypeDefaultDescription
variant'outline' | 'ghost''outline'
invalidbooleanField invalidaria-invalid, data-invalid and the danger styles. Inherits the enclosing Field when unset
resize'none' | 'vertical' | 'horizontal' | 'both''vertical'
rowsnumber4Native attribute; sets the initial height
fullWidthbooleanfalsew-full on the frame
unsavedbooleanfalseTints the field --input-unsaved and sets data-unsaved; pair with Label unsaved
buttonContentReactNodeRendered bottom-right inside the frame; the text is padded clear of it
disabledbooleanField disabledLeave unset to inherit from Field / Fieldset
classNamestringMerged last onto the frame (data-slot="textarea")
textareaClassNamestringMerged 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

SlotElementNotes
[data-slot="textarea"]divThe frame. Carries data-unsaved and Headless UI's data-focus / data-hover / data-disabled / data-invalid
[data-slot="textarea-control"]textareaThe native textarea; mirrors the same state attributes
[data-slot="textarea-actions"]divWraps buttonContent, absolutely positioned bottom-right
/* Consumer CSS: square fields */
[data-slot="textarea"],
[data-slot="textarea-control"] {
border-radius: 0;
}

Tokens

TokenUsed for
--control-padding-x-mdHorizontal padding of the native textarea
--input, --backgroundOutline border and fill
--input-unsavedunsaved tint
--ring, --dangerFocus ring colour; invalid state
--radius-mdCorner radius
--duration-fastColour transition

Accessibility

  • A native <textarea>; inside a Field it gets the field id, aria-labelledby from Label, aria-describedby from FieldDescription / FieldError, and disabled from the field or an enclosing Fieldset.
  • invalid sets aria-invalid="true" and data-invalid. Outside a Field, associate a label with htmlFor and describe errors with aria-describedby.
  • Focus shows a 1px ring hugging the border (focus-within), coloured --danger when invalid.
  • buttonContent sits inside the frame but after the textarea in DOM order, so its buttons are the next tab stop.
  • disabled renders the native attribute; the frame dims to 50% and shows a not-allowed cursor.