Components
Textarea
BetaMulti-line text input for longer-form content — special requests, travel notes, customer feedback, and itinerary descriptions.
Our team will do their best to accommodate your requests.
Purpose
Textarea collects longer free-form text where a single-line Input would be too constraining. Travel booking contexts include:
- Special requests — dietary, accessibility, room preference notes
- Customer feedback — post-holiday reviews and comments
- Custom itinerary notes — client brief for a tailor-made holiday
- Enquiry forms — "Tell us about your ideal holiday"
For single-line fields (name, email, destination) use the Input component. Use Textarea only when users genuinely need multiple lines.
Usage
Reach for Textarea when the expected answer is a sentence or more — special requests, feedback, or a free-form brief. For single-line fields such as name, email, or a destination search term, use Input instead; forcing a multi-line field for short answers only adds unnecessary vertical space to a form.
Textarea appears inside forms: booking special-request fields, post-holiday feedback forms, tailor-made itinerary briefs, and general enquiry forms.
Anatomy
We will try our best to accommodate your requests.
- 1Label — 14px medium weight label above the field. Required asterisk in danger colour.
- 2Textarea element — Resizable multi-line input with rounded-sm border. Minimum height set by rows prop.
- 3Placeholder — Grey-400 hint text inside the field. Disappears when user types.
- 4Error message — Danger text below field with AlertCircle icon. Replaces help text.
- 5Help text — Grey-500 guidance below field. Shown when no error.
- 6Character counter — Shown bottom-right when maxLength is set. Turns red when limit is reached.
Variants
Default
With character limit
The character counter appears when maxLength is set. It turns red when the limit is reached.
Maximum 300 characters.
0/300
Error state
Please describe your ideal holiday so we can help you.
Read-only
Resize control
Control whether the user can resize the textarea.
States
| State | Border | Background |
|---|---|---|
| Default | grey-300 | white |
| Focused | teal border + teal ring | white |
| Error | danger border + danger ring | white |
| Disabled | grey-300 | grey-50, opacity 60% |
| Read-only | grey-300 | grey-50, opacity 60% |
Best practices
Provide a descriptive placeholder that suggests the kind of content expected, especially for open-ended fields.
'Special requests' with a helpful placeholder guides users on what to write.
Render a Textarea with no label or placeholder on a form page.
An unlabelled blank box gives no guidance.
Set rows based on the expected length of input — 3-4 for short notes, 6-8 for longer descriptions.
rows={4} gives enough vertical space for a short paragraph without excessive blank space.
Use rows=1 — for single-line inputs, use the Input component instead.
rows={1} forces users to scroll horizontally to read what they have typed.
Set maxLength on fields where server-side constraints exist, pairing it with helpText to explain the limit.
maxLength={300} with counter gives users a clear budget and prevents overly long submissions.
Apply maxLength without displaying the character counter via the maxLength prop.
Without a character counter, users only discover the limit after form submission fails.
Use resize='none' when the textarea is inside a constrained layout where resizing would break the page.
resize='none' in a fixed-layout form keeps the form from breaking its grid.
Allow vertical resize (default) in forms where the textarea sits inside a grid with adjacent columns.
An unconstrained resize can push other form elements off screen on narrow viewports.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | — | ID for the textarea. Auto-generated from label if not provided. |
| label | string | — | Visible label above the field. |
| placeholder | string | — | Placeholder hint text inside the field. |
| value | string | — | Controlled value. Parent must provide onChange when using value. |
| defaultValue | string | — | Initial value for uncontrolled usage. |
| onChange | (event: ChangeEvent) => void | — | Called on every keystroke with the change event. |
| rows | number | 4 | Initial visible row count. Sets the textarea height. |
| maxLength | number | — | Maximum character count. Shows a counter below the field when set. |
| disabled | boolean | false | Disables the field. Applies cursor-not-allowed and grey background. |
| required | boolean | false | Marks field as required with a red asterisk and aria-required. |
| readOnly | boolean | false | Makes the field non-editable. Value is still selectable and copyable. |
| resize | 'none' | 'vertical' | 'horizontal' | 'both' | 'vertical' | Controls whether the user can resize the textarea. |
| error | string | — | Validation error message shown below. Switches border to danger colour. |
| helpText | string | — | Guidance text below the field. Hidden when error is set. |
| className | string | — | Additional Tailwind classes on the root container. |
Accessibility
Label association
The label is associated via htmlFor={id} — clicking the label focuses the textarea. When id is not provided, it is auto-generated from the label text.
Error and help text
Error messages have role="alert" so screen readers announce them on appearance. Help text and the character counter have aria-live="polite" and aria-atomic="true" so updates are announced.
<p role="alert" className="text-xs text-danger">
<AlertCircle size={12} />
Please describe your request.
</p>
<p aria-live="polite" aria-atomic="true">45/300</p>
Keyboard interaction
| Key | Action |
|---|---|
Tab | Focus the textarea |
Shift+Tab | Move focus to previous element |
| Any key | Insert character at cursor |
Enter | Insert line break |
Read-only vs disabled
readOnly— user can still focus and select text (for copying). Screen readers announce "read only".disabled— field is completely inert; excluded from form data submission and removed from tab order.
Related components
- Input — the single-line counterpart; use it for short fields like name, email, or destination.