Description
Provides a resizable, multi-line text input field that allows users to enter and edit longer freeform text responses.
Consultation response forms, stakeholder notes, meeting summary fields, feedback submissions, and any form context requiring paragraph-length input.
Single-line inputs create poor affordance and usability for longer content. The text area signals to users that extended responses are expected and provides the vertical space to compose them comfortably.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Label | Required | Describes the expected content. Must be associated via `for`/`id` or wrapping. Never use placeholder text as a substitute. |
| Textarea element | Required | The native ` |
| Helper text | Optional | Appears below the field. Use to clarify format expectations, length guidance, or constraints (e.g. 'Maximum 500 characters'). |
| Character counter | Optional | Displayed bottom-right when a maxlength is set. Updates live as the user types. Announced to screen readers when the limit is approaching. |
| Error message | Recommended | Replaces helper text on validation failure. Must be associated via `aria-describedby`. Paired with `aria-invalid="true"` on the textarea. |
| Resize handle | Optional | Visible affordance for manual vertical resizing. Present by default; suppress only when the field is in a constrained layout. |
Variants
Default
Standard undecorated textarea for general freeform input.
Most consultation or notes fields where no additional context is needed.
With helper text
Adds persistent guidance below the field.
When character limits, format constraints, or audience context needs to be communicated upfront (e.g. public-facing consultation forms).
With character counter
Displays remaining characters allowed when a maxlength is set.
Feedback fields with defined submission limits, SMS-length notifications, or any field with a hard character cap.
Read-only
Non-editable display of existing content within a form context.
Showing submitted stakeholder responses in review workflows, or displaying auto-generated summary text for confirmation.
Disabled
Field is present but unavailable for interaction.
When a textarea is conditionally required but the condition has not been met (e.g. a notes field that activates only after a prior selection).
States
| State | Behaviour |
|---|---|
| Default | Field displays with label, placeholder (if set), and default border using --op-color-border-default. |
| Focus | Border transitions to --op-color-interactive-focus with a 3px focus ring. Placeholder text remains visible until the user types. |
| Active (typing) | Focus ring persists. Character counter (if present) updates in real time. No visual change to the field border while content is valid. |
| Filled | Field retains entered content. Border returns to --op-color-border-default when focus is lost. |
| Error | Border colour changes to --op-color-status-error. Error message replaces helper text. `aria-invalid="true"` is applied to the textarea element. |
| Disabled | Field and label render at reduced opacity using --op-color-text-disabled. Cursor is `not-allowed`. Field is excluded from tab order. |
| Read-only | Content is visible and selectable but not editable. Border uses --op-color-border-default at reduced intensity. No focus ring on the field itself. |
Usage guidelines
When to use
- Capturing stakeholder notes, meeting minutes, or consultation responses that require more than one sentence.
- Collecting freeform public feedback in Social Point consultation forms.
- Allowing engagement officers to add context or rationale to a record (e.g. 'Reason for escalation').
- Survey or form questions that explicitly invite longer answers ('Please describe your concern in detail').
When not to use
- Short, structured answers (names, postcodes, phone numbers) — use Input instead.
- Content that needs formatting (headings, lists, links) — use Rich Text Editor instead.
- Capturing a single-select or multi-select response — use Select or Checkbox instead.
- Displaying read-only long-form content outside a form — use a standard text element or card layout instead.
Do / Don't
Do
Set an initial `rows` value that reflects the expected response length — short for a brief comment, taller for a detailed submission.
Don't
Default every textarea to 2 rows; this signals brevity and discourages the longer responses the field is designed for.
Do
Provide a clear, descriptive label that tells users exactly what to write (e.g. 'Your feedback on the draft transport strategy').
Don't
Use placeholder text as the only label — it disappears when the user starts typing and is not reliably accessible.
Do
Show a character counter and helper text when a maxlength is enforced, so users can manage their response length before hitting the limit.
Don't
Silently truncate content at a maxlength without informing the user.
Do
Allow vertical resize so users can expand the field to see their full response.
Don't
Disable resize entirely unless layout constraints make it unavoidable.
Layout & Spacing
Label to field gap: --op-space-4 (4px) Field internal padding: --op-space-8 (8px) vertical, --op-space-12 (12px) horizontal Field to helper text gap: --op-space-4 (4px) Minimum field height (default): 96px (approx. 3 rows at default line-height) Border radius: --op-radius-md (8px) Focus ring offset: 2px Focus ring width: 3px
Tokens
| Part | Token | Value |
|---|---|---|
| Label text | --op-color-text-primary | Full contrast label on all backgrounds. |
| Textarea background | --op-color-bg-primary | White/light background; use --op-color-bg-subtle in dense form contexts. |
| Textarea border (default) | --op-color-border-default | Neutral border at rest. |
| Textarea border (focus) | --op-color-interactive-focus | 3px focus ring, always visible; do not suppress. |
| Textarea border (error) | --op-color-status-error | Applied to border and error icon when aria-invalid is true. |
| Placeholder text | --op-color-text-placeholder | Must meet 3:1 contrast against --op-color-bg-primary (WCAG 1.4.3 non-text contrast threshold for UI components). |
| Input text | --op-color-text-primary | Typed content; full contrast. |
| Helper text | --op-color-text-secondary | Slightly reduced emphasis; still meets 4.5:1 against background. |
| Error message text | --op-color-status-error | Paired with an error icon for non-colour cue. |
| Character counter | --op-color-text-secondary | Transitions to --op-color-status-warning when within 20% of the limit, and --op-color-status-error when at or over. |
| Disabled state (all parts) | --op-color-text-disabled | Applied to label, border, and content text at reduced opacity. |
| Typography (input text) | --op-text-sm | 16px minimum on mobile to prevent browser zoom on iOS. |
Engineering notes
- Use `wa-textarea` from the Web Awesome component library wherever available. Fall back to a native `
- Always associate the label using `for`/`id`. Do not rely on wrapping alone — Web Awesome handles this internally, but native usage requires explicit association.
- Set `rows` as a starting size hint, not a hard constraint. The field should remain user-resizable (vertical only) unless the layout strictly requires a fixed height.
- When enforcing a character limit, set `maxlength` on the element AND display a live character counter. Update the counter on every `input` event, not just `change`.
- Announce the character counter to screen readers when the user is within 20% of the limit using an `aria-live="polite"` region — not on every keystroke.
- Apply `aria-invalid="true"` and `aria-describedby` pointing to the error message element when validation fails. Remove both attributes when the error is resolved.
- Do not use `resize: none` globally. Only suppress horizontal resize (`resize: vertical`) to prevent layout breakage while preserving user control.
- In consultation form contexts, auto-save draft input to localStorage or session state after a debounced `input` event to prevent data loss on accidental navigation.
- For read-only state, prefer `readonly` attribute over `disabled` — read-only content remains in the tab order and is included in form submissions; disabled content is not.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus into the textarea. Focus ring (3px, --op-color-interactive-focus) becomes visible. |
| Shift + Tab | Moves focus out of the textarea to the previous focusable element. |
| Enter | Inserts a line break within the textarea. Does not submit the form. |
| Arrow keys | Move the cursor within the text content as expected. |
| Escape | No default behaviour within the textarea itself. If the textarea is inside a modal or dialog, Escape should close the dialog (handled at the dialog level, not the textarea). |
Why it matters
Government consultation and feedback forms are often the primary or only channel for community members and stakeholders to submit formal input. Accessible text areas ensure that people using assistive technologies — including screen readers, switch access, and voice input — can participate in engagement processes on equal terms. This is a legal requirement under Australian DDA and WCAG 2.1 AA.
Focus
Focus must be clearly visible at all times. The 3px focus ring using --op-color-interactive-focus must not be suppressed or overridden. On error, focus should be programmatically moved to the textarea (or the first invalid field in the form) when the user attempts submission, so screen reader users are informed of the problem in context. Do not move focus automatically while the user is typing — this interrupts composition.
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
aria-label | Use only when a visible label cannot be provided (rare). Prefer a visible label associated via `for`/`id`. | <textarea aria-label="Additional comments"></textarea> |
aria-describedby | Associates helper text and/or error messages with the textarea so screen readers announce them after the field label. | <textarea id="feedback" aria-describedby="feedback-helper feedback-error"></textarea> |
aria-invalid | Set to `true` when the field has a validation error. Remove or set to `false` when the error is resolved. | <textarea aria-invalid="true" aria-describedby="feedback-error"></textarea> |
aria-required | Indicates the field must be completed before form submission. Use alongside a visible required indicator. | <textarea aria-required="true"></textarea> |
aria-live | Apply to the character counter container with value `polite` to announce remaining characters when the user is near the limit. | <span aria-live="polite" id="char-count">480 characters remaining</span> |
Contrast
Input text (--op-color-text-primary) against field background (--op-color-bg-primary): minimum 7:1 (AAA). Helper text (--op-color-text-secondary) against field background: minimum 4.5:1 (AA). Placeholder text (--op-color-text-placeholder) against field background: minimum 3:1 — verify this token meets the threshold; do not use a lighter value. Error message text (--op-color-status-error) against background: minimum 4.5:1 (AA). Always pair with a non-colour cue (icon or prefix text such as "Error:"). Focus ring (--op-color-interactive-focus, green-400) against adjacent background: minimum 3:1 non-text contrast (WCAG 1.4.11).
Touch targets
The textarea itself exceeds the 44x44px minimum due to its multi-line nature. Ensure the label and any adjacent controls (clear button, character counter toggle) individually meet the 44px minimum touch target height. On mobile, set font-size to at least 16px to prevent automatic zoom on iOS Safari.
Things to avoid
- Do not suppress the focus ring under any circumstances, including custom theme overrides.
- Do not use colour alone to communicate error state — always include an icon or text prefix alongside the error colour.
- Do not set `tabindex="-1"` on a textarea that users need to interact with.
- Do not auto-resize the field height dynamically while the user is typing — it causes layout shift that can disorient users and trigger screen reader re-reads.
- Do not place character limit information only in placeholder text — it will not be available after the user starts typing.