Text field
Where a clinician types something that will end up in a patient's record. Its boundary must be findable, its label must be permanent, and its errors must never destroy what was entered.
Overview
Text input is where the most ordinary-looking accessibility failures do the most clinical damage. A placeholder used as a label vanishes the moment someone types. A 1.1:1 border makes the field itself hard to locate. An error that clears the input costs a clinician the measurement they just read off a monitor.
None of those are edge cases — they are the default behaviour of most component libraries, including stock shadcn's border treatment. This component fixes them and the token gate keeps them fixed.
Findable
The boundary of the control is visible without hovering or guessing, at 3:1 against every surface it can sit on.
Permanently labelled
The label is outside the field and stays there. Nothing that identifies an input disappears when the input is used.
Never destructive
Validation explains; it does not clear, reformat or reject silently. What the clinician typed survives until they change it.
Anatomy
| Element | Rule |
|---|---|
| Label | A real <label for>, above the field, always visible.
Never a placeholder. |
| Input | 44 px tall — the touch floor — with a --input border at ≥ 3:1.
16 px text minimum, which also prevents iOS zoom-on-focus. |
| Hint | Units, format, or what "blank" means. Wired with aria-describedby so it
is announced, not just seen. |
| Error | Replaces the hint, states what is wrong and what to do. Two-pixel border plus text — never colour alone. |
Stock shadcn sets --input equal to --border, which in this
palette measured 1.10:1 against the surfaces it sits on — far below the 3:1
that WCAG 2.2 SC 1.4.11 requires for identifying a control. --input is therefore a
governed token, gated in CI against
background, card and muted surfaces. A divider may be faint; a control boundary
may not.
Labels, not placeholders
Label and unit both survive typing. A colleague reading over a shoulder knows what 1240 is.
The placeholder that said "Peak troponin (ng/L)" is gone. The number is now unitless and unlabelled in a clinical record.
Placeholders may be used for examples of format — e.g. 1964-03-11 —
never for the name of the field, and never as the only statement of a required unit.
Validation
- Validate on blur, not on keystroke. Flagging "invalid" while someone is halfway through typing an MRN is noise that trains people to ignore the error state.
- Never clear, truncate or reformat the user's input on error. A field that empties itself costs a value the clinician may have read once off a monitor.
- Errors name the fix. "MRN must be 9 digits — 7 entered. Check the wristband and re-enter", not "Invalid input".
- Never block submission silently. If a form cannot submit, say which field and move focus there.
- Warnings are not errors. A physiologically unusual but possible value gets a confirmation, not a rejection — the patient may genuinely have a troponin of 25 000.
Clinical input rules
| Kind | Rule |
|---|---|
| Measurements | Unit shown as a permanent label or suffix, never assumed. Value and unit are stored together. |
| Identifiers | Mono stack, no auto-capitalisation, no autocorrect, no autocomplete from a browser profile. Grouping is display-only; the stored value is unformatted. |
| Dates | ISO 8601 with a visible format hint. Never a locale-ambiguous mask. |
| Free-text clinical notes | Never truncated silently. If a limit exists it is shown from the start with a live remaining count. |
| Passwords | Paste always permitted. A reveal toggle is provided. Never impose composition rules that force written-down credentials. |
Browser autofill on an MRN or patient-name field can suggest a different patient
the clinician entered earlier that shift. Patient identifier inputs set
autocomplete="off", autocorrect="off" and
spellcheck="false". Credential fields do the opposite — they carry correct
autocomplete tokens so password managers work.
States
Read-only and disabled are different. Read-only is a value the clinician may read and copy but not change — model version, acquisition time. Disabled is an input that is not currently applicable, and it always carries adjacent text saying what would make it applicable. A disabled field with no explanation is a dead end.
Do's and don'ts
Autofill and autocorrect off, mono stack so 1 and l cannot be confused.
Browser autofill on an MRN field, offering a different patient the clinician saw an hour ago.
type="text" with inputmode. A scroll gesture over the field scrolls the page.
type="number". A scroll over the focused field silently changes the recorded value.
Accessibility
- Every field has a
<label for>.aria-labelis a fallback for genuinely label-less controls, not a shortcut. - Hints and errors are wired with
aria-describedbyso they are announced with the field, not stranded visually beside it. aria-invalidon error, alongside the 2 px border and the message. Three channels, no reliance on colour.- Focus ring at ≥ 3:1 against both the field and the surface behind it.
Never
outline: none. - 44 px minimum height and 16 px text. Meets the touch floor and prevents mobile zoom-on-focus.
- Correct
inputmodeso a numeric keypad appears on touch — without usingtype="number", whose spinners and scroll-wheel behaviour cause silent value changes. - Errors announced politely on blur, never assertively on every keystroke.
Outcomes of use
What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.
- Effectiveness — The value recorded matches the value intended — correct units, nothing truncated, no autofill substitution from another patient.
- Efficiency — Entry time, and re-entry rate after validation. A field that clears itself on error doubles the cost of every mistake.
- Satisfaction — Confidence that what was typed was accepted as typed. Silent reformatting erodes this faster than an explicit rejection does.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).
- Visible boundary at ≥ 3:1, governed. Mitigates: entering data into the wrong field, or failing to find one.
- Permanent labels and units. Mitigates: an unlabelled or unit-less value entering the record.
- Input never cleared or reformatted on error. Mitigates: loss of a value read once from a device.
- Out-of-range confirms rather than rejects. Mitigates: a true extreme value being made un-enterable.
- No autofill on patient identifiers. Mitigates: wrong-patient data entry from a suggestion.
- No
type="number". Mitigates: silent value change from a scroll wheel or stray spinner press. - Disabled fields explain themselves. Mitigates: a clinician unable to proceed and unable to see why.
- ISO dates with visible format. Mitigates: day/month transposition.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/text-field.json
// Label, hint and error wiring is done by the component — the ids and
// aria-describedby cannot be forgotten at the call site.
<TextField
label="Peak troponin"
hint="ng/L. Leave blank if not yet resulted."
inputMode="numeric"
value={value}
onChange={setValue}
onBlur={validate}
error={error} // sets aria-invalid + replaces hint
/>
// Patient identifier — autofill and correction disabled.
<TextField
label="Patient identifier"
variant="identifier" // mono, autocomplete/correct/spellcheck off
hint="9 digits, as printed on the wristband"
/>
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | — | Required. Rendering without one fails lint. |
variant | text | identifier | measurement | secret |
text |
Sets the font stack and the autocomplete / correction policy. |
unit | string | — | Required for measurement; rendered as a permanent suffix. |
hint | string | — | Wired via aria-describedby. |
error | string | — | Sets aria-invalid. Never clears the value. |
readOnly | boolean | false |
Value is selectable and copyable. |
disabled | boolean | false |
Requires hint explaining the precondition. |
Related
- Sign in / sign out — credential fields and paste.
- Clinician override — the optional free-text reason.
- Tokens — why
--inputis governed. - Typography — identifiers and units.