Text field
Usage
Use a text field for short, single-line answers: plate numbers, names, odometer readings, part numbers. Use a textarea for notes longer than a sentence, a select or radios when the answer comes from a short known list, and a combobox when the list is long.
- Always inside a form field with a visible label.
- Set the right
type,inputmodeandautocomplete— on a mechanic's tablet that decides which keyboard appears. - Size the field to the expected answer (a postcode field is narrow) with
--field-max. - Placeholders are optional examples ("e.g. KX-219"), never instructions or labels.
Anatomy
- Container —
input.input, ordiv.input.input--affixedwhen it has affixes; 1px--input-border, 8px radius - Value / placeholder text
- Prefix (optional) —
.input__affixbefore.input__control: currency, icon - Suffix (optional) —
.input__affixafter the control: unit
Examples
Hover and focus-visible are frozen with .is-hover and .is-focus-visible, which share their rules with the real pseudo-classes.
Text field
Vehicles can't change depot while a work order is open.
Error: Enter an email address like name@depot.com.
€
km
<input class="input" id="plate" name="plate" type="text" placeholder="e.g. KX-219">
<input class="input input--sm" …> <input class="input input--lg" …> <!-- heights match .btn--sm / .btn--lg -->
<!-- States are attributes -->
<input class="input" … disabled>
<input class="input" … readonly>
<input class="input" … aria-invalid="true" aria-describedby="email-error">
<!-- Prefix / suffix: the border moves to the wrapper; reference the affix so it is announced -->
<div class="input input--affixed">
<span class="input__affix" id="cost-prefix">€</span>
<input class="input__control" id="cost" inputmode="decimal" aria-describedby="cost-prefix">
</div>
<div class="input input--affixed">
<input class="input__control" id="odometer" inputmode="numeric" aria-describedby="odometer-unit">
<span class="input__affix" id="odometer-unit">km</span>
</div>
API
| Hook | Values | Purpose |
|---|---|---|
.input | block class | Bordered text input (on input, or on the wrapper when affixed) |
.input--sm | --lg | modifier class | 32 / 40 (default) / 48px — same as buttons |
.input--affixed, .input__control, .input__affix | modifier / parts | Prefix and suffix inside the border |
disabled / readonly | attribute | Not editable (disabled also not focusable or submitted) |
aria-invalid="true", :user-invalid | attribute / pseudo-class | Invalid styling; pair with a .field__error |
type, inputmode, autocomplete | attribute | Keyboard and autofill (email, tel, postal-code…) |
.is-hover | .is-focus-visible | docs-only class | Freezes a state |
Do and don't
readonly.Accessibility
| Key | Behavior |
|---|---|
| Tab | Focuses the field (read-only fields are focusable, disabled ones are skipped) |
| Typing, ← →, Home End | Native text editing |
- Role: native
textbox(orsearchboxfortype="search"). Name from<label for>; hint, affix and error viaaria-describedby. - Focus: the 2px teal focus ring plus a focus-colored border, so focus doesn't rely on a subtle border change. With affixes the ring wraps the whole field.
- Invalid: border doubles in weight and turns red, an icon and message appear — never color alone.
:user-invalidonly applies after interaction, so empty required fields aren't red on load. - Boundary contrast:
--input-borderiscolor.border.strong, checked at 3:1 against the canvas. - Font size is 16px at md and lg, so iOS doesn't zoom on focus; heights of 32–48px meet the 24px target minimum.
Tokens
| Custom property | Purpose |
|---|---|
--input-border, --input-bg, --input-radius | Component tokens |
--size-control-sm | -md | -lg | Heights |
--color-text-muted | Hover border, affix text |
--color-border-focus, --focus-ring-* | Focus |
--color-border-invalid | Invalid border |
--color-bg-subtle, --color-border-default | Read-only |
--color-bg-muted, --color-border-subtle, --color-text-disabled | Disabled |
--color-text-subtle | Placeholder |