KKUONIDesign System

Components

Textarea

Beta

Multi-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.

  1. 1
    Label14px medium weight label above the field. Required asterisk in danger colour.
  2. 2
    Textarea elementResizable multi-line input with rounded-sm border. Minimum height set by rows prop.
  3. 3
    PlaceholderGrey-400 hint text inside the field. Disappears when user types.
  4. 4
    Error messageDanger text below field with AlertCircle icon. Replaces help text.
  5. 5
    Help textGrey-500 guidance below field. Shown when no error.
  6. 6
    Character counterShown 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


Read-only


Resize control

Control whether the user can resize the textarea.

States

StateBorderBackground
Defaultgrey-300white
Focusedteal border + teal ringwhite
Errordanger border + danger ringwhite
Disabledgrey-300grey-50, opacity 60%
Read-onlygrey-300grey-50, opacity 60%

Best practices

Do

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.

Don't

Render a Textarea with no label or placeholder on a form page.

An unlabelled blank box gives no guidance.

Do

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.

Don't

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.

Do

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.

Don't

Apply maxLength without displaying the character counter via the maxLength prop.

Without a character counter, users only discover the limit after form submission fails.

Do

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.

Don't

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

PropTypeDefaultDescription
idstringID for the textarea. Auto-generated from label if not provided.
labelstringVisible label above the field.
placeholderstringPlaceholder hint text inside the field.
valuestringControlled value. Parent must provide onChange when using value.
defaultValuestringInitial value for uncontrolled usage.
onChange(event: ChangeEvent) => voidCalled on every keystroke with the change event.
rowsnumber4Initial visible row count. Sets the textarea height.
maxLengthnumberMaximum character count. Shows a counter below the field when set.
disabledbooleanfalseDisables the field. Applies cursor-not-allowed and grey background.
requiredbooleanfalseMarks field as required with a red asterisk and aria-required.
readOnlybooleanfalseMakes the field non-editable. Value is still selectable and copyable.
resize'none' | 'vertical' | 'horizontal' | 'both''vertical'Controls whether the user can resize the textarea.
errorstringValidation error message shown below. Switches border to danger colour.
helpTextstringGuidance text below the field. Hidden when error is set.
classNamestringAdditional 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

KeyAction
TabFocus the textarea
Shift+TabMove focus to previous element
Any keyInsert character at cursor
EnterInsert 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.
  • Input — the single-line counterpart; use it for short fields like name, email, or destination.