01
Anatomy
A text input has more parts than people think. Label. Field. Helper text. Error text. Required mark. They all need a predictable position, and the position has to survive across every form in the product.
The default in too many systems: label above the field, helper below the field, error below the helper. Three vertical positions, three different messages. When the helper is hidden and only the error shows, the field jumps down a row. The user's eye follows the wrong thing.
I keep them in a single track. Label, field, then one message slot underneath that switches between helper and error. The slot reserves its line-height even when empty. The field never moves.
- —Label above the field. Every time. Inline labels are clever, and they break on mobile, and they break for screen readers.
- —One message slot. Helper or error, never both at once.
- —The slot keeps its space when empty. No layout shift when validation fires.
- —Required marker on the label, not the field. The asterisk is conventional but inaccessible on its own — pair it with aria-required.
“If your error makes the page move, your form has a layout bug, not a validation bug.”
02
The attributes that matter
The HTML you write is the keyboard the user sees and the autofill they get. Get it wrong and you're fighting the browser for the rest of the project.
<input type="email"
autocomplete="email"
inputmode="email"
autocapitalize="off"
autocorrect="off" />
<input type="tel"
autocomplete="tel"
inputmode="tel" />
<!-- Existing password (sign-in) -->
<input type="password" autocomplete="current-password" />
<!-- New password (sign-up, password reset) -->
<input type="password" autocomplete="new-password" />“type validates. inputmode keyboards. They are not the same attribute, and people confuse them every day.”
The single best return-on-effort: write the autocomplete value correctly. Browsers and password managers depend on it. autocomplete="off" is mostly ignored by Chrome and Safari now — if you're trying to suppress autofill, you've already lost. Design around the autofill instead of fighting it.
03
Mobile mechanics
Mobile is where attribute choices stop being polish and start being usability. Two things matter more than anything else: the 16-pixel font-size floor, and inputmode.
The 16-pixel rule
Set the font-size of any text input below 16px on iOS Safari and the page will zoom in when the field is focused. The fix is one line of CSS.
input,
textarea,
select {
font-size: 16px;
}If the design uses smaller type elsewhere, scale the inputs separately. Don't try to fight the zoom with viewport meta hacks — they break pinch-to-zoom for everyone else, including the users who actually need it.
inputmode — the cheap win
inputmode sets the on-screen keyboard without changing what the field will accept. The five worth memorizing:
- —numeric — number pad, no decimal, no minus. PINs, ZIP codes, account numbers.
- —decimal — number pad with a decimal key. Currency, weights, quantities.
- —tel — phone keypad with #, *, +. Telephones.
- —email — keyboard with @ visible. Email fields.
- —url — keyboard with .com and / visible. URL fields.
04
Placeholder is not a label
“If your label disappears when typing starts, you don't have a label. You have a hint.”
Placeholder-as-label is the most common form mistake I see. The reasons it fails are the reasons it keeps getting used — it looks tidy in a comp.
- —The label vanishes the moment the user starts typing. They've forgotten what they were filling in. Watch any usability session and count how often someone deletes their answer to re-check the field they're on.
- —Screen readers read the placeholder as supplementary, not as the field's name. The field is effectively unlabelled to anyone using assistive tech.
- —Default placeholder color is low-contrast by browser convention. Users with low vision can't see the field is interactive at all.
- —Translation length explodes placeholders. The English label fits; the German label doesn't, and the placeholder is now silently truncated.
Float-labels solve some of these, not all. The label still moves; the contrast still drops at rest. A static label above the field, full contrast, is the form that survives every redesign cycle. Boring. Right.
05
Disabled vs readonly
Two states that look similar on screen and behave nothing alike. I see them picked wrong more often than picked right.
Disabled
Non-interactive AND not submitted with the form. Greyed out completely. The field's value is dropped on submit, the field is announced as not-applicable by screen readers, and the user can't even focus it to read it. Use when the field shouldn't exist for this user — e.g., a payment-method dropdown that isn't relevant for free accounts.
Readonly
Non-interactive but submitted with the form. The user can focus it, copy from it, and screen readers read its value. Use when the value is set elsewhere (server, prior step, computed) and shouldn't change here — e.g., an order ID, a calculated total, a confirmed shipping address.
06
Validation belongs to the next part
This piece is about the field itself. When validation fires, where the error appears, the grammar of the error message — that's its own article in the cross-cutting track.
The short version, so you can leave the field alone until then: don't validate on every keystroke. Validate on blur for things you can check ("that's not a valid email"). Validate the whole form on submit for things you can't check until you try to use them ("that account number doesn't exist"). And write errors as instructions to take an action — "Add your phone number" — not labels of a state — "Phone is required."
Text inputs are the field type users meet most often. The mechanics above aren't polish — they're the difference between a form that gets filled and a form that gets abandoned.