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 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.

Loading example

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.

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 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.

Loading example

Props

TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> with:

PropTypeDefaultDescription
variant'outline' | 'ghost''outline'
state'default' | 'error' | 'success''default'Border and ring colour; error also marks the control invalid. Exposed as data-state
invalidbooleanFormItem invalidaria-invalid, data-invalid and the error styles. Inherits the enclosing FormItem when unset
resize'none' | 'vertical' | 'horizontal' | 'both''vertical'
rowsnumber4Native attribute; sets the initial height
fullWidthbooleanfalsew-full on the frame
unsavedbooleanfalseTints the field --input-dirty and sets data-unsaved; pair with Label isDirty
buttonContentReactNodeRendered bottom-right inside the frame; the text is padded clear of it
disabledbooleanFormItem disabledLeave unset to inherit from FormItem / 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-state, 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-dirtyunsaved tint
--ring, --destructive, --successFocus ring colour per state
--radius-mdCorner radius
--duration-fastColour transition

Accessibility

  • A native <textarea>; inside a FormItem it gets the field id, aria-labelledby from Label, aria-describedby from Description / Message, and disabled from the item or an enclosing Fieldset.
  • invalid (or state="error") sets aria-invalid="true" and data-invalid. Outside a FormItem, associate a label with htmlFor and describe errors with aria-describedby.
  • Focus shows a 1px ring hugging the border (focus-within), coloured by the validation state.
  • 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.