# Textarea Field A form field for collecting multi-line free-form text Section: components Docs: https://nebula.kaluza.com/components/textarea-field --- ## 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](/components/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](/components/text-field) 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](/components/summary-list) to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context. ## Anatomy 1. **Label** 2. **Hint** 3. **Textarea input** 4. **Error message** 5. **Required/Optional indicator** ## Basic usage ```tsx ``` ## 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. ```tsx ``` ### 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. ```tsx ``` ### 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. ```tsx ``` ### Common form features For validation, hint text, required/optional labelling, and field width, see the [Form guidance](/patterns/forms). These features are shared across all Nebula form fields. ## Grouping Textarea fields are typically part of a larger form. See the [Form component](/components/form) and [form guidelines](/patterns/forms) for guidance on grouping and layout. ## Rules and guidance ### Usage - **TextareaField** MUST NOT be passed the disabled prop. Rationale: The 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. [id: textareafield.no-disabled; testability: auto] - **TextareaField** SHOULD be used when the user's input is expected to span multiple lines or be of unpredictable length. [id: textareafield.use-for-multiline; testability: context] - **TextareaField** SHOULD be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot edit. Rationale: A field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability. [id: textareafield.non-editable-as-text; testability: manual] - **TextareaField** SHOULD NOT be used for inputs that will contain a single short value such as a name or reference number. Rationale: Single-line inputs signal to the user that a short response is expected. Use TextField instead. [id: textareafield.not-for-single-line; testability: context] - **TextareaField** SHOULD NOT be used to collect structured data that could be broken into discrete fields. Rationale: Multiple focused fields are easier to validate and less error-prone than parsing free text. [id: textareafield.not-for-structured-data; testability: context] ### Placement - **rows prop** SHOULD be set to reflect the expected volume of text the user will enter. Rationale: A 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. [id: textareafield.rows-matches-expected-content; testability: manual] - **rows prop** SHOULD NOT be set below three for inputs where the user may enter more than a short phrase. [id: textareafield.rows-minimum; testability: auto] ### Content - **Label** MUST be present and describe what the user should enter. [id: textareafield.label-required; testability: manual] - If maxLength attribute is used: **Hint** MUST communicate the character limit when maxLength is set on the textarea. Rationale: Users need to know the limit before they start typing to avoid having their text truncated. [id: textareafield.hint-for-maxlength; testability: manual] - **Hint** SHOULD explain the expected format or content when it may not be obvious from the label alone. [id: textareafield.hint-guidance; testability: manual] ### 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 prop. [id: textareafield.resize-vertical-only; testability: auto] - **resizable prop** SHOULD be set to true when the amount of text the user might enter is unpredictable. Rationale: Allows users to expand the field to suit their needs without scrolling within a fixed-height input. [id: textareafield.resizable-for-unpredictable-length; testability: context] ### Validation - **Error message** MUST describe what the user needs to do to correct their input, not just that an error occurred. [id: textareafield.error-describes-correction; testability: manual] - If TextareaField is used inside a Form component: **Error message** SHOULD be supplied via the Form component's errors prop and name prop rather than the error prop directly. Rationale: Form context error handling ensures consistent error routing across the form without manual wiring. [id: textareafield.error-from-form-context; testability: auto] - **TextareaField** SHOULD be validated on form submission rather than while the user is typing. Rationale: In-flight validation interrupts the user before they have finished composing their response. [id: textareafield.validate-on-submit; testability: manual] ## Accessibility ### 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html) ### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html) ### 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 each test covers: - 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. What these statuses mean: - 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. 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. ## Properties ### TextareaField Also accepts all props from `HTMLAttributes`, `TextareaHTMLAttributes`. - `id` (type `string`, required). 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` (type `ReactNode`, optional). 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` (type `string`, required). Visible label for the field. It is associated with the input, so it forms the input's accessible name. - `name` (type `string`, optional). The `name` attribute passed to the input. Also used to look up the field's error from a surrounding `Form`. - `hint` (type `ReactNode`, optional). 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` (type `boolean`, optional, default `false`). 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` (type `number`, optional). 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` (type `'always' | 'never' | 'small'`, optional, default `'small'`). 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` (type `number`, optional, default `25`). 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` (type `number`, optional, default `0`). Extra width in pixels added to the width calculated from `characters`, to make room for icons or buttons inside the input. - `resizable` (type `boolean`, optional, default `false`). Allows the user to resize the textarea vertically by dragging its corner. When false, resizing is disabled. - `autoComplete` (type `string`, optional). 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` (type `number`, optional, default `3`). Number of visible lines of text.