Designer view

Textarea

A multi-line plain-text editing control for free-form prose — comments, feedback, descriptions, support-ticket bodies. Native HTML `<textarea>` is canonical; carries `aria-multiline=true` implicitly. Distinct from TextInput by the multi-line value shape (line-breaks preserved on submission), by the `rows` axis, and by the auto-resize contract that grows the field as content fills.

Also called Multi-line text input Comment box Text area

When to use

Use

For free-form prose where the user may write more than a single line — comments, reviews, support-ticket bodies, feedback fields, descriptions. Use auto-resize when the natural input length is unpredictable and a fixed rows value either wastes space or forces internal scrolling too eagerly. Use fixed rows when the surrounding layout has firm vertical bounds (sidebar fields, dense forms). Pair with a character-counter when the field has a canonical maxlength that aids the user.

Avoid

For single-line inputs — that is `TextInput`. For structured input (rich-text, code, formula) — that is a dedicated rich-text editor or code-block editor; native textarea does not support inline formatting. For very short bounded prose (under ~40 chars) — TextInput plus soft-warning is leaner. Never use textarea where the value is conceptually a single line that may overflow — overflow handling on a single-line `<input>` is the correct surface.

Versus related

  • text-input

    `TextInput` is a single-line `<input type="text">` whose value never contains line breaks; `Textarea` is a multi-line `<textarea>` that preserves line breaks on submission. The axes diverge: textarea ships `rows`, `resize`, and auto-resize axes; text-input does not. Both share the label association rules and the form-integration discipline (Constraint Validation API, autocomplete tokens). Decision test: does the value carry meaningful line-breaks (Textarea) or is it a single-line value (TextInput)?

  • combobox

    `Combobox` commits one value from a constrained list with optional typeahead filter; `Textarea` accepts free-form arbitrary multi-line text. Combobox uses a listbox popup; Textarea is a flat field. Use Combobox for structured single-value choice; Textarea for unstructured prose entry.

Textarea is the canonical multi-line-prose-input primitive — a resizable rectangular field that accepts arbitrary line-broken text. Standalone canonical (not a TextInput variant) because the axes diverge: `rows`, `resize` direction, and the auto- resize contract have no analog in the single-line surface. Two variants — standard fixed-rows-with-manual-resize and auto-resize bounded by `minRows` plus `maxRows` — and an optional character-counter slot that announces remaining characters via `aria-live`. The reference documents the resize-blocks-zoom anti-pattern (a WCAG 1.4.10 reflow risk), the auto-resize-thrash performance pitfall, and the divergence from TextInput.

Highlight
Fig 1.1 · Textarea · Designer view
Designer

Figma anatomy

Slot Figma type Hint
root frame Auto-layout vertical frame; label + textarea + description + character-count
label text Text element above textarea; size matches form-label hierarchy
textarea frame Multi-line text frame; min-height bound to rows token; resize-handle visual on bottom-end corner
description text Smaller text below textarea; muted color; visibility per "has description" property
error-message text Error-toned text below textarea; visibility per "has error" property
character-count text Right-aligned smaller text below textarea; muted by default, error-toned when over limit
Designer

Token usage per slot

root
spacing
  • gapspacing.tight
label
color
  • foregroundcolor.text.primary
typography
  • sizetext.sm
  • weightweight.medium
textarea
spacing
  • paddingspacing.compact
radius
  • cornerradius.sm
color
  • backgroundcolor.surface.bg
  • bordercolor.border.strong
typography
  • sizetext.sm
description
color
  • foregroundcolor.text.muted
typography
  • sizetext.xs
error-message
color
  • foregroundcolor.text.danger
typography
  • sizetext.xs
character-count
color
  • foregroundcolor.text.muted
typography
  • sizetext.xs
Both

Figma ↔ Code property map

FigmaKindCodeNotes
VariantEnumvariantstandard (fixed `rows`) / auto-resize (grows from `minRows` to `maxRows` based on content).
SizeEnumsizesm / md / lg.
ResizeEnumresizenone / vertical / horizontal / both. Maps to CSS `resize` property; controls the browser-provided handle availability. Default `vertical` to keep WCAG 1.4.10 reflow intact.
RowsNumberrowsInitial line-count. For auto-resize variant, this is the `minRows` lower bound.
Max RowsNumbermaxRowsAuto-resize-only — ceiling at which the textarea stops growing and switches to internal scrolling.
DisabledBooleandisabled
ReadonlyBooleanreadonlyPrefer over `disabled` when the value is locked but should remain readable / tabbable / submittable.
RequiredBooleanrequiredPairs visual asterisk with the `required` attribute.
InvalidBooleaninvalidPairs `aria-invalid="true"` with the visual error treatment.
LabelTextchildren of `<label>`Visible label text. Placeholder is not a substitute.
PlaceholderTextplaceholderOptional example value ("e.g. Steps to reproduce") — never the field meaning.
DescriptionTextdescriptionOptional supporting prose, programmatically linked via `aria-describedby`.
Error MessageTexterrorMessageVisibility bound to invalid state.
Character CountSlotcharacterCount`aria-live="polite"` so SR announces on typing-pause. Threshold-visibility pattern (visible above ~75% of `maxlength`) is the GOV.UK canonical reference.
Designer

Motion

TransitionDuration token
autoResizeAdjustmotion.duration.fast
focusRingFademotion.duration.fast
Easing
motion.easing.standard
Reduced motion
Instant (jump cut)
Both

Internationalisation

RTL · mirroring

Textarea inherits `dir` from the document or accepts explicit `dir` attribute. Under `direction: rtl`, the caret starts at the inline-end (right side) and text flows right-to-left. Resize handle position swaps to the bottom-inline-start corner via logical positioning. The character-counter alignment (typically right-end in LTR) flips to left-end via `text-align: end`.

Text expansion

Label text expands 30-50% under translation. Description and error-message expand similarly; reserve no fixed width on text slots. The textarea field itself accommodates expansion naturally because the value is user-supplied, not translated. Character-counter text ("X / Y characters" → "X / Y caractères" or "X / Y Zeichen") follows generic prose expansion. Auto-resize variants need no special handling for expansion because the height adapts to content.

Both

Variants, properties, states

Variants

Structurally different versions of the component.

standard auto-resize

Properties

The same component, parameterised.

PropertyType
size sm | md | lg
resize none | vertical | horizontal | both
disabled boolean
readonly boolean
required boolean
invalid boolean

States

Browser/user-driven (interactive) vs. app-driven (data).

KindStates
interactive
hoverfocus-visibleactivedisabled
data
emptyfilledinvalidat-limit
Both

State transitions

FromToTrigger
emptyfilledUser types into the textarea
filledemptyUser clears the textarea content
filledinvalidValidation fails (over maxlength, under minlength, required-but-empty on submit)
filledat-limitUser reaches the character-count threshold (typically 75-100% of maxlength)
Both

Figma↔Code mismatches

  1. 01
    Figma

    Textarea drawn with fixed pixel height

    Code

    Textarea sized by `rows` attribute (line-count) plus auto-resize for content-driven growth

    Consequence

    Designers ship "120 px tall textarea"; developers know the canonical surface is `rows` (line-count) which varies by font-size, line-height, and platform. The two surfaces diverge — the Figma frame height does not match the rendered textarea height across browsers (Mac vs Windows font-rendering shifts visible row count). Auto-resize variants expand beyond the Figma frame, breaking the design illustration.

    Correct

    Document size as `rows` plus `size` token. Figma carries one frame per row count (3-row, 5-row, 8-row illustrative variants); auto-resize variant carries an annotation for `minRows` and `maxRows` bounds. Code uses the `rows` attribute or computes height from content.

  2. 02
    Figma

    Resize-handle drawn as a static decorative element

    Code

    Resize-handle is a native browser affordance controlled by `resize` CSS property

    Consequence

    Designers paint a custom three-dot resize handle in the bottom-right corner; developers rely on the browser- provided handle (default) which renders differently across browsers and platforms. The two surfaces look different. Designers may not realise the handle is browser-controlled and try to position custom controls.

    Correct

    Document the resize handle as browser-provided in the anatomy. Figma uses a generic visual placeholder (or omits it) with an annotation noting browser-rendering. Custom resize handles require completely overriding `resize: none` plus pointer-event handling — heavy and rarely worth the divergence from native.

  3. 03
    Figma

    Auto-resize variant drawn at a single representative height

    Code

    Auto-resize textarea grows from `minRows` to `maxRows` based on content

    Consequence

    Designers ship one auto-resize variant frame at "this is roughly the typical content height"; developers ship a textarea that ranges from 2 rows to 12 rows (or whatever bounds). The Figma file does not illustrate the empty / filled / overflow states, so designers cannot review the boundary behaviours.

    Correct

    Document `minRows` and `maxRows` as canonical properties. Figma carries three frames (empty at minRows, filled at typical height, overflowing at maxRows-with-internal-scroll). Code computes height from content via `scrollHeight` measurement on input events.

  4. 04
    Figma

    Character-counter drawn as plain static text

    Code

    Character-counter is `aria-live="polite"` so SR announces the count on typing-pause

    Consequence

    Designers draw "120 / 500" as static text under the textarea; developers wrap it in a live-region. The visual is identical but the AT contract is invisible in the Figma file — designers may move the counter to a non-live span without realising the regression.

    Correct

    Document the character-counter slot as `aria-live="polite"` in the anatomy. Figma's counter slot carries an annotation for the live-region behaviour. Code wraps the counter element in a live-region or sets `aria-live` on the element itself.

Both

Contracts

Non-negotiable contracts

  1. APGWAI-ARIA textbox + label patterns

    The textarea has a programmatically-associated label — either `<label for="<id>">` or a wrapping `<label>`. Adjacent prose and placeholder text are not substitutes. The label is the canonical accessible name.

    Without the association, SR users hear "edit text, multi-line" without the field meaning. The programmatic contract is what AT relies on; visual proximity is sighted-only and breaks under voice navigation, mobile SR, and accessibility tooling.

  2. WCAGWCAG 1.4.10 Reflow

    `resize: none` is restricted to layouts that auto- resize themselves OR fields with short `maxlength` bounds. Default `resize: vertical` so users with zoom requirements (WCAG 1.4.10 Reflow) can adjust the field. Implementations that lock resize without compensating for low-vision zoom block accessible content entry.

    Without resize freedom, low-vision users at 200% zoom encounter content that does not fit or is truncated. The reflow contract is one of the canonical 2.x AA requirements; locking resize is the most common single textarea regression in accessibility audits.

  3. Canon

    Auto-resize implementations coalesce height-recalc to one measurement per ~50 ms (or per `requestAnimationFrame`). Recalculating on every input event without throttle drops frames on commodity hardware and breaks typing cadence on slower devices.

    Without coalescing, textareas with auto-resize become unusable on tablets, low-end Android, and older laptops. The performance threshold is invisible in static design review but surfaces immediately in real-device QA — and by then the implementation is harder to fix.

Vocabulary drift

HTML
`<textarea>`
Native HTML element. `aria-multiline=true` is implicit; do not set explicitly. Default `rows="2"` per spec; canonical practice sets a meaningful initial size. Line-breaks preserve on form submission as `\n`.
WAI-ARIA
`role="textbox"` (with `aria-multiline=true`)
WAI-ARIA textbox role. Native textarea provides this implicitly; custom-rendered multi-line surfaces (rare — `contenteditable` is the alternative) need the role plus `aria-multiline` plus key handling.
Material 3
Text field (multi-line variant)
Material 3 codifies multi-line as a variant of the text field component, not a separate primitive. Same canonical contract; the spec splits multi-line guidance into a dedicated specs page.
Polaris
TextField (multiline prop)
Polaris exposes multi-line as a `multiline` boolean prop on TextField. Same canonical contract; the prop activates auto-resize and the dedicated textarea anatomy.
Atlassian
Textarea
Atlassian ships Textarea as a separate component with `resize`, `minimumRows`, `maxHeight` props. Canonical name match; the auto-resize bounds (minimumRows + maxHeight) align with the canonical `minRows` + `maxRows` axis.
GOV.UK
Textarea + Character count
GOV.UK ships Textarea plus a separate Character count component (textarea with built-in counter). The canonical anatomy folds character-count as an optional slot; GOV.UK's threshold-visibility pattern is a canonical optimisation captured in the slot prose.
Designer

Common mistakes

Blocker

#textarea-no-label

Textarea has no programmatically-associated label

Problem

The textarea renders with no `<label for>`, no wrapping `<label>`, and no `aria-labelledby`. SR users hear "edit text, multi-line, blank" without the field meaning. Form filling becomes blind for AT users.

Fix

Wrap the textarea in `<label>` (the textarea becomes a child of the label) or pair it with `<label for="<id>">`. Either form establishes the canonical association. Adjacent paragraphs and placeholder text are NOT substitutes — the AT contract is programmatic.

Major

#textarea-placeholder-as-label

Field meaning lives in placeholder text instead of a label

Problem

The textarea has no visible label; placeholder text ("Tell us more...") communicates the meaning. When the user starts typing, the placeholder disappears and the meaning is lost. SR may or may not announce the placeholder; users with cognitive load or short-term memory needs cannot recall what the field asks.

Fix

Always render a visible `<label>` with the field meaning. Placeholder text is reserved for example content ("e.g. Steps to reproduce") that supplements but does not replace the label. The visible label stays present; the placeholder is auxiliary.

Major

#textarea-resize-blocks-zoom

`resize: none` plus narrow content area blocks WCAG 1.4.10 reflow

Problem

The textarea is rendered with `resize: none` to fit a tight design layout. On narrow viewports or under user zoom (e.g. 200% zoom for low-vision users), the textarea content overflows or becomes unreadable because the user cannot enlarge the field. Violates WCAG 1.4.10 (Reflow).

Fix

Default to `resize: vertical` so users can extend the field. Reserve `resize: none` for layouts where the surrounding context auto-resizes (sidebar panels, full- bleed modals) AND the field's `maxlength` is short enough that overflow is unlikely. Test at 200% zoom to verify content remains readable.

Major

#textarea-autoresize-thrash

Auto-resize recalculates layout on every keystroke without debounce

Problem

Auto-resize textarea measures `scrollHeight` and updates its CSS `height` on every `input` event. On long-form content with rapid typing, the layout recalculates hundreds of times per second — janks the input, drops frames, and on slower devices breaks typing cadence. Visible as cursor lag and stuttering.

Fix

Debounce the height-recalc via `requestAnimationFrame` coalescing (one measure per frame) or via input-event throttling (one measure per ~50 ms). Alternative mechanism: hidden mirror element with the same content and CSS, measured via offsetHeight without triggering layout-recalc on the textarea itself. Both patterns are well-documented in mature libraries (react-textarea- autosize, autosize.js).

Major

#textarea-character-count-no-aria-live

Character-counter updates silently for SR users

Problem

The character-counter ("120 / 500") renders as static text and updates as the user types, but is not in a live-region. SR users hear the field but never the counter; they cannot tell when they are approaching the limit and may submit invalid content.

Fix

Wrap the counter in `aria-live="polite"` so SR announces the count on typing-pause. For threshold- visibility patterns (GOV.UK), make the count visible and live only above ~75% of the limit so AT users get the warning when it matters. Avoid `aria-live="assertive"` — every keystroke announcement is hostile.

Accessibility hints
Slot Accessibility hint
root The root holds programmatic associations between the textarea and its label / description / error / character-count via `for` and `aria-describedby`. No ARIA role is needed on the root itself — the textarea carries the textbox role natively.
label Label text is the textarea's accessible name. SR users hear "Comments, edit text, blank, multi-line" or the equivalent per the SR. Required-field markers (visual asterisk) live in the label slot but the canonical signal is `required` attribute on the textarea, not the visual asterisk.
textarea Native `<textarea>` provides the role and the `aria-multiline=true`. Never substitute `<div contenteditable="true">` for a textarea — the contenteditable surface lacks form-submission participation, lacks placeholder-and-required attributes, and ships inconsistent SR support across browsers. `aria-invalid="true"` plus `aria-describedby` linking to error-message activate validation surfacing.
description Associate via `aria-describedby` on the textarea. SR announces description after the label and the field state. Avoid using description for required-field markers — use `required` attribute on the textarea plus visible asterisk in the label.
error-message `aria-describedby` on the textarea includes the error- message id; `aria-invalid="true"` triggers SR error announcement on focus. The error-message element carries `aria-live="polite"` so async-validation errors announce when they appear without forcing focus moves.
character-count `aria-live="polite"` so the counter announces on typing-pause without interrupting active typing. `aria-describedby` on the textarea may include the counter id so SR users hear the limit on field focus. Avoid `aria-live="assertive"` — interrupting every keystroke is hostile.