KKUONIDesign System

Components

Input

Stable

Input fields let people enter text, choose from a list, or write longer notes. A unified component covers single-line text, select dropdowns, and textareas.

Purpose

The Input component is the primary data-entry surface across every Kuoni form — traveller details, search widgets, budget fields, notes sections, and dropdown pickers. A single component covers three element types through the as prop:

  • as='input' (default) — single-line free text; covers names, emails, passport numbers, dates.
  • as='select' — a styled dropdown for a fixed set of options; use when choices are fewer than ~15 items and exhaustive.
  • as='textarea' — multi-line text for longer, open-ended content like special requests or notes.

Inputs must always carry a visible label. Placeholders alone are not sufficient — they disappear the moment someone types, and they fail minimum contrast ratios. Every error must explain both what went wrong and how to fix it.

Usage

Reach for Input whenever a form needs free text, a fixed dropdown choice, or a longer note — it is the default data-entry building block. For a calendar date specifically, use DatePicker instead of type="date", since it gives a consistent cross-browser calendar UI. For a small bounded numeric count (traveller numbers, rooms), use CounterInput rather than a free-text number field.

Input appears throughout booking forms, search widgets, account settings, and any other data-entry surface in the product.

Anatomy

£pp

We send confirmations here.

  1. 1
    LabelPersistent text above the field. Tied to the input via htmlFor/id so clicking it focuses the field.
  2. 2
    PrefixOptional text adornment inside the left edge of the field — e.g. "£" for currency, "+44" for phone.
  3. 3
    Left icon slotOptional 14–16 px icon at the left of the field — e.g. a search magnifier.
  4. 4
    Input fieldThe editable area. Height is fixed at 44 px for comfortable touch targets.
  5. 5
    Right icon slotOptional icon at the right. For select, always replaced by a chevron.
  6. 6
    SuffixOptional text adornment inside the right edge — e.g. "pp" for per-person pricing.
  7. 7
    Help textGuidance shown below the field when there is no error.
  8. 8
    Error textValidation message with warning icon. Turns the border red and announces via aria-describedby.

Variants

Text input

The default element. Use for names, email addresses, passport numbers, search queries, and any other short free-form text.


Select (dropdown)

Use as='select' and pass <option> elements as children. The chevron icon is always rendered; iconRight is ignored for select.

When to use select: The set of choices is fixed and complete. Fewer than ~15 options. A free-text fallback does not make sense (e.g. country codes, room types, number of nights).


Textarea

Use as='textarea' for open-ended content such as special requests, dietary notes, or feedback. The field resizes vertically. Default row count is 4; customise with rows.


With left icon

Pass any React node (typically a 14–16 px Lucide icon) to iconLeft. Common uses: search fields, date pickers, email fields.


With prefix and suffix

prefix and suffix render as bordered text adornments attached to the field. Use for units, currency symbols, or domain suffixes.

£pp

States

Default and focus

The border moves from #CCCCCC (grey-300) to a 2 px teal ring (#005B55) on focus. The ring has zero offset so it sits flush with the border.

Default
London
Focus

Error state

When validation fails, the border turns red, a warning icon appears beside the error message, and aria-invalid="true" is set on the field. Always write a message that explains how to fix the problem.


Disabled

Disabled fields show a grey background and 60% opacity. They are excluded from tab order and cannot be edited.


Required

The required prop adds a red asterisk after the label (hidden from screen readers) and injects a visually-hidden "(required)" text for assistive technology. The native required attribute is also set for browser validation.

Sizes

All single-line inputs and selects use a fixed height of 44 px (h-11). This meets the WCAG 2.1 AA 44 × 44 px minimum touch-target size and creates visual consistency across forms. Textareas use auto height based on the rows prop.

ElementHeightTouch target
input44 px✅ WCAG AA
select44 px✅ WCAG AA
textareaAuto (rows × line-height)Resizable by user

Best practices

Do

Always provide a visible label above every input field.

A persistent label stays visible while the user types.

Don't

Use placeholder text as the only label for a field.

The placeholder disappears on input and fails contrast requirements.

Do

Write error messages that name the issue and describe the fix: "Enter a valid email address."

Specific messages like this tell users exactly how to fix the problem.

Don't

Use vague error messages like "Invalid input" or "Error."

The user knows the input is invalid — they need to know why and what to do.

Do

Use select for a fixed, exhaustive list of fewer than 15 options.

Select is scannable and faster than typing when choices are fixed.

Don't

Use a text input when only specific values are valid (e.g. country, number of travellers).

Typing a country code is error-prone and slow compared to a dropdown.

Do

Use prefix/suffix for currency, units, or domain hints (£, pp, @domain.com).

Prefix and suffix reduce ambiguity without breaking the field layout.

Don't

Encode units in the placeholder text where they will disappear on input.

Mixing label text and units inside the placeholder creates confusion.

Props

PropTypeDefaultDescription
as'input' | 'select' | 'textarea''input'HTML element to render. Use select for dropdowns, textarea for multi-line text.
labelstringVisible label above the field. Tied to the input via htmlFor/id.
idstringHTML id. Auto-derived from label (kebab-cased) when omitted.
typestring'text'Native input type: 'text', 'email', 'password', 'number', 'tel', 'date', etc. Ignored for select and textarea.
placeholderstringHint text shown when the field is empty. Supplement to — not replacement of — the label.
helpTextstringGuidance text shown below the field. Hidden when error is set.
errorstringValidation message. Triggers red border, warning icon, and aria-invalid="true".
requiredbooleanfalseMarks the field as required. Adds red asterisk (visual) and "(required)" text (screen reader).
disabledbooleanfalseDisables the field. Applies grey background and 60% opacity.
iconLeftReactNodeIcon node rendered at the left edge of the field. Recommend 14–16 px Lucide icons.
iconRightReactNodeIcon node rendered at the right edge. Ignored when as="select" (chevron is always shown instead).
prefixstringText adornment attached to the left edge of the field. Mutually exclusive with iconLeft.
suffixstringText adornment attached to the right edge of the field. Mutually exclusive with iconRight.
rowsnumber4Number of visible text rows for textarea. Ignored for input and select.
childrenReactNodeOption elements for as="select". Pass <option> nodes as children.
classNamestringAdditional Tailwind classes on the wrapper div.

Accessibility

Label association

Every Input must have a label prop. The component connects the label to the field via htmlFor / id automatically. Clicking the label focuses the input — important for small touch targets.

Never omit a label in favour of a placeholder. Placeholders disappear on input and are excluded from most screen-reader navigation modes.

Error announcement

When error is set:

  • aria-invalid="true" is applied to the field element.
  • aria-describedby points to the error paragraph element.
  • The error paragraph has role="alert" so assistive technology announces the message immediately when it appears.

Required fields

The required prop applies both the native required attribute (triggering browser validation) and injects a visually-hidden "(required)" text for screen readers. The red asterisk is marked aria-hidden="true" to avoid double-announcement.

Indicate at the top of long forms that asterisked fields are required: "Fields marked * are required."

Keyboard interaction

KeyAction
TabMove focus to the next interactive element
Shift + TabMove focus to the previous element
Enter / SpaceOpen select dropdown (browser native)
Arrow keysNavigate select options when open

Colour and contrast

Error state relies on both the red border colour and the error message text — never colour alone. The error text uses #C0392B (5.4:1 contrast on white — WCAG AA) and includes a warning icon for redundancy.

  • DatePicker — use for calendar date selection instead of a native date input.
  • CounterInput — use for small bounded numeric counts instead of a free-text number field.