On this page
Textarea Field
A form field for collecting multi-line free-form text
- Overview
- Rules and guidance
- Properties
- Styles
- Accessibility
When to use
- When the user needs to enter free-form text that is likely to span multiple lines, such as a description, notes, or an explanation
- When the length of the response is unpredictable and cannot be constrained to a single line
- When the content the user writes may benefit from being visible in full while they compose it
When not to use
- For single-line inputs such as names, phone numbers, or reference codes - use Text Field instead, which signals to the user that a short response is expected.
- For structured data that can be broken into discrete fields - an address, for example, is better collected with multiple Text Fields than a single textarea.
- When only a small number of characters are expected - the visual size of the textarea communicates how much text is expected. A large textarea for a short answer creates a mismatch that confuses users.
- To indicate that a field is unavailable - the disabled state is not supported. Use Summary List to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context.
Anatomy
- Label
- Hint
- Textarea input
- Error message
- Required/Optional indicator
Basic usage
Edit live code
<TextareaField id="description" label="Description" />
Variants and features
Controlling height with rows
Use the rows prop to set the initial visible height of the textarea. The default is three rows. Set rows to match the expected volume of text. A field for a short description might need three rows; one for a detailed complaint or explanation might need eight or more.
Edit live code
<TextareaField id="description-rows" label="Description" rows={5} />
Resizable
Set resizable to allow users to drag the bottom of the textarea to increase its height. Use this when the amount of text the user might enter is unpredictable. The textarea can only be resized vertically.
Edit live code
<TextareaField id="description-resizable" label="Description" resizable />
Character count
Use the characterLimit prop to show a live "count/limit" counter below the input. The limit is advisory: it does not stop the user typing past it, but the counter turns to the error colour and screen readers are told how many characters remain or are over.
To set the width of the textarea rather than a counter, use characters instead.
Edit live code
<TextareaField id="comment" label="Comment" characterLimit={50} />
Common form features
For validation, hint text, required/optional labelling, and field width, see the Form guidance. These features are shared across all Nebula form fields.
Grouping
Textarea fields are typically part of a larger form. See the Form component and form guidelines for guidance on grouping and layout.
usage
- TextareaField must not be passed the disabled propThe prop passes through to the native textarea with no Nebula styling and removes the field from the tab order, making it invisible to keyboard users with no accessible indication of why.Copy rule ID for textareafield.no-disabled
- TextareaField should be used when the user's input is expected to span multiple lines or be of unpredictable lengthCopy rule ID for textareafield.use-for-multiline
- TextareaField should be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot editA field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability.Copy rule ID for textareafield.non-editable-as-text
- TextareaField should not be used for inputs that will contain a single short value such as a name or reference numberSingle-line inputs signal to the user that a short response is expected. Use TextField instead.Copy rule ID for textareafield.not-for-single-line
- TextareaField should not be used to collect structured data that could be broken into discrete fieldsMultiple focused fields are easier to validate and less error-prone than parsing free text.Copy rule ID for textareafield.not-for-structured-data
placement
- rows prop should be set to reflect the expected volume of text the user will enterA textarea that is too small for the expected content forces the user to scroll within it; one that is too large implies more text is expected than is needed.Copy rule ID for textareafield.rows-matches-expected-content
- rows prop should not be set below three for inputs where the user may enter more than a short phraseCopy rule ID for textareafield.rows-minimum
content
- Label must be present and describe what the user should enterCopy rule ID for textareafield.label-required
- If maxLength attribute is usedHint must communicate the character limit when maxLength is set on the textareaUsers need to know the limit before they start typing to avoid having their text truncated.Copy rule ID for textareafield.hint-for-maxlength
- Hint should explain the expected format or content when it may not be obvious from the label aloneCopy rule ID for textareafield.hint-guidance
interaction
- TextareaField will restrict resize to vertical only when resizable is true. The component sets resize: vertical via CSS and cannot be overridden via the propCopy rule ID for textareafield.resize-vertical-only
- resizable prop should be set to true when the amount of text the user might enter is unpredictableAllows users to expand the field to suit their needs without scrolling within a fixed-height input.Copy rule ID for textareafield.resizable-for-unpredictable-length
validation
- Error message must describe what the user needs to do to correct their input, not just that an error occurredCopy rule ID for textareafield.error-describes-correction
- If TextareaField is used inside a Form componentError message should be supplied via the Form component's errors prop and name prop rather than the error prop directlyForm context error handling ensures consistent error routing across the form without manual wiring.Copy rule ID for textareafield.error-from-form-context
- TextareaField should be validated on form submission rather than while the user is typingIn-flight validation interrupts the user before they have finished composing their response.Copy rule ID for textareafield.validate-on-submit
TextareaField
Also accepts all props from HTMLAttributes, TextareaHTMLAttributes.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
id | string | — | Yes | Unique id for the input, used to link the label, hint and error to it. Also used as the base for related ids such as `{id}-error`. |
error | ReactNode | — | No | Error message shown below the input. When set, the input gets `aria-invalid` and an error border, and the message is linked to it with `aria-describedby`. Takes precedence over an error provided by a surrounding `Form`. |
label | string | — | Yes | Visible label for the field. It is associated with the input, so it forms the input's accessible name. |
name | string | — | No | The `name` attribute passed to the input. Also used to look up the field's error from a surrounding `Form`. |
hint | ReactNode | — | No | Helper text shown below the label. It is part of the label element, so it is read as part of the input's accessible name. |
optional | boolean | false | No | Marks the field as not required. This appends "(optional)" to the label (or, when the theme marks required fields, hides the required asterisk) and sets `aria-required` on the input only when the field is not optional. |
characterLimit | number | — | No | Maximum number of characters, shown as a live "count/limit" counter below the input. The limit is advisory: it is not enforced on the input, but the counter turns to the error colour and screen readers are told how many characters remain or are over. |
fullWidth | always never small | 'small' | No | Controls when the field stretches to the full width of its container. `always` is always full width, `never` always uses the width set by `characters`, and `small` is full width on small screens only. |
characters | number | 25 | No | Width of the input, as an approximate number of characters (the width is set to `0.8em` per character, rounded up). Does not limit how much can be typed. |
affixWidth | number | 0 | No | Extra width in pixels added to the width calculated from `characters`, to make room for icons or buttons inside the input. |
resizable | boolean | false | No | Allows the user to resize the textarea vertically by dragging its corner. When false, resizing is disabled. |
autoComplete | string | — | No | Hint to the browser's autofill, for example `email`. When the `disableAutocomplete` theme config flag is enabled this defaults to `off`; a value passed here overrides that. |
rows | number | 3 | No | Number of visible lines of text. |
This component has no styles of its own to document.
In brief
Textarea Field is a native multi-line text input with a visible label. A screen reader announces the label (hint included), then the field, and reads an error as its description.
What Nebula provides
- A real label linked to the textarea by id, so clicking the label focuses it and gives a larger target.
- aria-required on the textarea unless the field is optional, which adds "(optional)" to the label.
- An error sets aria-invalid on the textarea and links the message to it with aria-describedby. The message has an icon as well as colour.
- With characterLimit, the limit is the textarea's description and a live region announces the characters remaining once typing pauses.
Keyboard behaviour
The textarea is a native textarea, one tab stop reached with Tab, and it shows a focus ring when focus comes from the keyboard. The resize handle, when resizable is set, is the browser's own and works with a pointer only.
Interactions
- Tab - Moves focus to the textarea
Accessible design
Never use the placeholder as the label or the hint
A placeholder disappears as soon as the user types, and Nebula shows it at half opacity. Put the question in the label and any extra guidance in the hint.
WCAG: 3.3.2 Labels or Instructions
Keep the hint short
The hint sits inside the label element, so a screen reader reads it as part of the field's name. Put what the user needs first in the label and keep the hint to one short line.
WCAG: 3.3.2 Labels or Instructions
Write the error so it says what to change
"Comment must be 50 characters or fewer" tells the user what to do and "Invalid" does not. The message is read with the field, so it has to make sense on its own.
WCAG: 3.3.3 Error Suggestion
Accessible development
Always pass errors through error, or through a Form with the field name
This is what sets aria-invalid and links the message to the textarea. Text placed beside the field is not announced with it.
WCAG: 3.3.1 Error Identification
If a characterLimit is set, validate it on submit
The limit is advisory: typing past it is never blocked and the counter only changes colour and announces the excess. Show an error when the form is submitted over the limit, so the problem is stated in text.
WCAG: 3.3.1 Error Identification
Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
|---|---|---|
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What these tests and statuses mean
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
The tests
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
The statuses
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.