Design System
Forms

Input

A single-line text field for short, free-form responses — names, amounts, references. For longer text use Textarea; for a fixed set of choices use Select or Combobox.

Purpose

Use an Input when the answer is short and unconstrained. If you can enumerate the valid answers, prefer a selection control — typing is the most error-prone way to collect data.

Use it for

  • Names, email addresses, phone numbers
  • Amounts and short references (claim ID, invoice number)
  • Search terms

Reach for something else

  • Multi-line content → Textarea
  • One of a known list → Select / Combobox
  • Several of a known list → MultiSelect; free-form lists → TagsInput
  • Dates → DatePicker / DateRangePicker

Anatomy

Seven parts; Field wires the label, hint and error to the control for you.

We only use this for claim updates.

  1. Label — always visible, paired via htmlFor. Never replaced by a placeholder.
  2. Required marker — destructive-coloured asterisk from Field's required prop.
  3. Input container — 40px tall, --input border, --radius-md corners, --background fill.
  4. Leading icon — optional 18px glyph, muted; composed with a relative wrapper.
  5. Placeholder / value — 14px text; placeholder in --muted-foreground, value in --foreground.
  6. Hint text — 12px muted helper under the control; linked with aria-describedby.
  7. Error message — replaces the hint; 12px destructive text plus aria-invalid on the control.

States

Six states cover the lifecycle. Focus and error are shown with their styles applied for illustration.

Enter a valid claim number (CLM-00000).

Icons & addons

Adornments are composition recipes, not props — wrap the Input and pad the affected side.

+60
MYR
Prefix and suffix addons
// Prefix: the addon owns the left corners, the input drops them.
<div className="flex">
  <span className="inline-flex items-center rounded-l-md border border-r-0 border-input bg-muted px-3 text-sm text-muted-foreground">
    +60
  </span>
  <Input type="tel" placeholder="12 345 6789" className="rounded-l-none" />
</div>

// Leading icon: absolute-position the glyph and pad the input past it.
<div className="relative">
  <Glyph
    symbol="search"
    size={18}
    className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
  />
  <Input placeholder="Search members…" className="pl-9" />
</div>

Structure

The tokens behind the control — change these, and every input follows.

PropertyValue
Height40px (h-10)
Padding12px horizontal (px-3)
Typography14px / Inter 400 (text-sm)
Border1px --input
Radius--radius-md (6px)
Focusborder --ring + 2px --ring at 40%
Errorborder --destructive (via aria-invalid)
Disabled50% opacity, not-allowed cursor

Best practices

Do

  • Keep the label visible at all times — pair every Input with a Label or wrap it in Field.
  • Match the input type to the value (email, tel, password) so mobile keyboards adapt.
  • Put format guidance in the hint, not the placeholder — placeholders vanish on typing.
  • Mark required fields with Field's required marker and validate on blur or submit.
  • Size the field to the expected content — a postcode field should not be full-width.

Don't

  • Use a placeholder as the only label — it fails once the field is filled.
  • Use type="number" for identifiers (NRIC, policy or claim numbers) — leading zeros drop and scroll changes values; use inputMode="numeric" instead.
  • Rely on colour alone for errors — always pair the red border with a message.
  • Disable paste or autocomplete — both are accessibility and security aids.
  • Use Input for multi-line content — that is Textarea's job.

Accessibility & keyboard

Field does the wiring; these are the guarantees the pattern gives you.

  • Label and control are linked with htmlFor; hint and error are announced via aria-describedby.
  • Errors set aria-invalid and always pair colour with a written message.
  • Focus shows a 2px --ring outline — never remove it.
  • All text meets WCAG 2.2 AA in both themes, including the placeholder.
  • Keyboard: Tab / Shift+Tab move between fields; standard text editing inside; Enter submits the owning form.

Props

Input is a styled native <input> — it accepts every native attribute (type, inputMode, autoComplete, maxLength…) and adds nothing on top. Compose labels, hints and errors with Field:

PropTypeDefaultDescription
labelReact.ReactNodeLabel text rendered above the control.
htmlForstring | undefined`id` of the wrapped control; links the label and derives the hint/error `id`s.
requiredboolean | undefinedfalseAppends a required marker (a destructive-coloured asterisk) to the label.
hintReact.ReactNodeHelper text shown under the control; hidden while `error` is present.
errorReact.ReactNodeError message; when set, it replaces the hint and the field renders in its error state.
classNamestring | undefinedExtra classes for the field wrapper.
childrenrequiredReact.ReactNodeThe form control (and any adornments) the field wraps.

On this page