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.
- Label — always visible, paired via
htmlFor. Never replaced by a placeholder. - Required marker — destructive-coloured asterisk from
Field'srequiredprop. - Input container — 40px tall,
--inputborder,--radius-mdcorners,--backgroundfill. - Leading icon — optional 18px glyph, muted; composed with a relative wrapper.
- Placeholder / value — 14px text; placeholder in
--muted-foreground, value in--foreground. - Hint text — 12px muted helper under the control; linked with
aria-describedby. - Error message — replaces the hint; 12px destructive text plus
aria-invalidon 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.
// 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.
| Property | Value |
|---|---|
| Height | 40px (h-10) |
| Padding | 12px horizontal (px-3) |
| Typography | 14px / Inter 400 (text-sm) |
| Border | 1px --input |
| Radius | --radius-md (6px) |
| Focus | border --ring + 2px --ring at 40% |
| Error | border --destructive (via aria-invalid) |
| Disabled | 50% opacity, not-allowed cursor |
Best practices
Do
- Keep the label visible at all times — pair every Input with a
Labelor wrap it inField. - Match the input
typeto 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; useinputMode="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 viaaria-describedby. - Errors set
aria-invalidand always pair colour with a written message. - Focus shows a 2px
--ringoutline — 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:
| Prop | Type | Default | Description |
|---|---|---|---|
label | React.ReactNode | — | Label text rendered above the control. |
htmlFor | string | undefined | — | `id` of the wrapped control; links the label and derives the hint/error `id`s. |
required | boolean | undefined | false | Appends a required marker (a destructive-coloured asterisk) to the label. |
hint | React.ReactNode | — | Helper text shown under the control; hidden while `error` is present. |
error | React.ReactNode | — | Error message; when set, it replaces the hint and the field renders in its error state. |
className | string | undefined | — | Extra classes for the field wrapper. |
childrenrequired | React.ReactNode | — | The form control (and any adornments) the field wraps. |
TogglePlanned
A two-state button that stays pressed. Use it for a setting that takes effect immediately, not as a substitute for a checkbox in a form.
Select
Pick exactly one option from a known list. Built on Radix Select: keyboard type-ahead, portalled listbox, announced value. For searchable or long lists use Combobox; for several choices use MultiSelect.