Textarea
Usage
Use a textarea for answers longer than a sentence: defect descriptions, handover notes, reasons for a rejected inspection. Use a text field for single-line answers like a plate number or a part code.
- Always inside a form field with a visible label that says what to write ("Defect description", not "Notes").
- Size it to the expected answer with
rows; use.textarea--auto-growwhen length varies a lot. - When there is a limit, show a counter. Don't use
maxlength: it silently cuts pasted text.
Anatomy
- Container —
textarea.textarea; same border, radius and states as the text field - Value / placeholder text
- Resize handle (vertical only; hidden when auto-grow, read-only or disabled)
- Counter (optional) —
.field__counterwired withdata-counter
Examples
Hover and focus-visible are frozen with .is-hover and .is-focus-visible, which share their rules with the real pseudo-classes.
Textarea
Notes are locked once the work order is closed.
Error: Enter the reason the vehicle failed inspection.
Grows as you type, up to 12 lines.
Sent as a text message to the driver's phone.
Up to 160 characters
<textarea class="textarea" id="defect" name="defect" rows="4"></textarea>
<textarea class="textarea" … disabled> <textarea class="textarea" … readonly>
<textarea class="textarea" … aria-invalid="true" aria-describedby="defect-error"></textarea>
<!-- Auto-grow (field-sizing: content where supported; rows + resize elsewhere) -->
<textarea class="textarea textarea--auto-grow" rows="2" style="--textarea-max-rows: 12"></textarea>
<!-- Counter: js/form-field.js → init(root) -->
<textarea class="textarea" id="msg" data-counter="msg-count" aria-describedby="msg-count"></textarea>
<p class="field__counter" id="msg-count" data-max="160">Up to 160 characters</p>
API
| Hook | Values | Purpose |
|---|---|---|
.textarea | block class | Bordered multi-line input |
.textarea--auto-grow | modifier class | Content-sized between min and max rows |
--textarea-min-rows, --textarea-max-rows | custom property (number) | Auto-grow bounds (3 / 12) |
rows | attribute | Initial height and the no-field-sizing fallback |
data-counter + .field__counter[data-max] | attribute / part | Live counter (js/form-field.js); over the limit sets aria-invalid but never blocks typing |
disabled / readonly / aria-invalid="true" | attribute | States |
.is-hover | .is-focus-visible | docs-only class | Freezes a state |
Do and don't
Accessibility
| Key | Behavior |
|---|---|
| Tab | Moves focus into and out of the textarea (Tab is never captured) |
| Enter | Inserts a new line |
| Typing, arrows, Home End | Native text editing |
- Role: native multi-line
textbox. Name from<label for>; hint, counter and error viaaria-describedby. - Counter: visible text updates on every keystroke; a separate polite status region announces it after a typing pause.
- Invalid: thicker red border, icon and message — never color alone.
:user-invalidonly applies after interaction. - Text is 16px so iOS does not zoom; resize is vertical only so it never breaks the layout at 320px.
Tokens
| Custom property | Purpose |
|---|---|
--input-border, --input-bg, --input-radius | Shared with text field |
--size-control-md | Minimum height (2 × control) |
--color-border-focus, --focus-ring-* | Focus |
--color-border-invalid | Invalid border |
--color-bg-subtle, --color-bg-muted, --color-text-disabled | Read-only and disabled |
--color-text-subtle | Placeholder |