Components
Text field
The most-used component in any back-office tool, and the one where getting the error state wrong is most expensive.
When to use it
For short, free-form input. If the set of valid answers is known and small, use a Select; if it is known and very small, use radios, which show all options without a click.
When not to
- Never without a visible label. A placeholder is not a label: it disappears the moment someone types, which is exactly when they need it, and it fails at low contrast by design.
- Not for dates or numbers with structure. A text field asking for a date gets every format a person can invent.
- Not sized to hint at content length. A short input for a postcode looks tidy and breaks the moment someone has a longer one.
States
We use this for the receipt only.
Enter an address in the form [email protected]
Set when the account was created.
Write the error, not the rule
"Invalid input" tells someone they are wrong and nothing else. "Enter an address in the form [email protected]" tells them what to type next. The second is barely longer and removes the guess.
Markup
<div class="ht-field">
<label class="ht-field__label" for="email">Work email</label>
<input class="ht-field__input" id="email" type="email"
aria-describedby="email-hint" />
<p class="ht-field__hint" id="email-hint">
We use this for the receipt only.
</p>
</div> <div class="ht-field">
<label class="ht-field__label" for="email">Work email</label>
<input class="ht-field__input" id="email" type="email"
aria-invalid="true" aria-describedby="email-error" />
<p class="ht-field__error" id="email-error">
<span aria-hidden="true">!</span>
Enter an address in the form [email protected]
</p>
</div> Validation timing
Validate on blur, not on every keystroke — flagging an email as invalid while someone is still typing the domain is noise. Re-validate on input only after a field has already failed once, so the error clears as soon as it is fixed rather than lingering until they leave the field again.
Accessibility
| Requirement | Why |
|---|---|
| <label for> | Names the field, and makes the label a click target that focuses the input — which matters most on touch. |
| aria-describedby | Links hint and error text so both are announced with the field rather than orphaned after it. |
| aria-invalid | The programmatic error state. Without it the red border means nothing to anyone not looking at it. |
| type | Sets the mobile keyboard. type="email" saves a person three taps every time. |
The border uses --ht-border-interactive, which is held to 3:1 for WCAG 1.4.11 non-text contrast — an input a person cannot find is not an input.