Select
Choosing one value from a known set. The component's defining risk is not how it opens — it is what it says before anyone has chosen.
Overview
A select looks like a low-stakes control and behaves like a high-stakes one, because it always shows something. Whatever is in the closed trigger reads as an answer, and if the form is submitted without the clinician ever opening it, that answer is recorded as theirs.
Everything below follows from that: an unanswered select must look unanswered, options must be readable in full, and no clinical value is ever pre-chosen.
Unanswered by default
An untouched select shows a placeholder, not a value. Nothing clinical is chosen on a clinician's behalf.
Fully legible
Every option is readable in full before it is chosen. An option a clinician has to guess at is a choice they did not really make.
Reversible
Changing a selection is as easy as making one, and never destroys anything else on the form.
Anatomy
- Non-ischaemic ST elevation
- Early repolarisation
- Pericarditis
- Left ventricular hypertrophy
- Signal
- Artefact
- Lead misplacement
| Element | Rule |
|---|---|
| Label | Always a real <label for> outside the control. Same rule as
Text field. |
| Trigger | 44 px tall, --input border at ≥ 3:1. Placeholder text is visibly
distinct from a chosen value, and not by colour alone. |
| Group headings | Real grouping semantics, never a disabled option used as a divider. |
| Options | Full text, wrapping if necessary. Never truncated. |
A select showing the first option by default records that option every time a clinician does not notice the field. Anything with clinical meaning — a reason, a severity, a route, a site, a dose band — opens on a placeholder and stays invalid until chosen. The exception is narrow: a value the system genuinely knows, such as the acquiring device or the current user's unit, which is pre-filled and clearly marked as such.
Options
- Never truncate. "Left ventricular hypertroph…" and "Left ventricular hypertrophy with strain" are different findings. Options wrap; the list widens; the label gets shorter. Nothing gets an ellipsis.
- Order by clinical likelihood, not alphabetically, where a defensible ordering exists — and state the ordering in the hint if it is not obvious.
- Group when there are more than about seven, using real group semantics.
- Add search above about fifteen. Below that, search costs more than it saves.
- Include an explicit "Other" with free text wherever the list could be incomplete — a clinician forced into the nearest wrong option produces worse data than one who can type.
- Never disable options without saying why. Same rule as Interaction states.
Options that look alike
A select presents its options in a small box, in a list, read quickly, often by someone who already believes they know which one they want. That is the worst possible presentation for a pair of names that differ by two letters — and clinical vocabularies are full of them.
Confusable options are never adjacent, and never distinguished by their name alone. If two options can be mistaken for each other, the list separates them and each carries something that tells them apart — strength, route, form, or the clinical context it belongs to.
Look-alike and sound-alike names are a recognised medication-safety hazard with published lists behind it, and a dropdown concentrates every condition that makes them dangerous: small type, tight leading, alphabetical order that puts the confusable pair next to each other, and a reader scanning rather than reading.
| Mitigation | What it does | Claim class |
|---|---|---|
| A disambiguating attribute on every option | Strength, route or form beside the name. Two options that differ only in a name are two options that can be swapped; two that differ in name and route are harder to swap by accident. | Reasoned |
| Confusable pairs separated in the ordering | Alphabetical order actively creates the hazard by placing similar names together. Grouping by clinical category instead puts distance between them. | Reasoned |
| Tall Man lettering | Capitalising the differing part — the convention used on published confused-name lists. Adopt it if your formulary already uses it, so the screen matches the shelf and the chart. | Convention. Widely used and endorsed; the experimental evidence for benefit is mixed rather than settled, and this system does not claim more than that. |
| The chosen option restated after selection | The closed trigger shows the full option including its disambiguator, not a truncated name. This is where a mis-selection is actually caught. | Reasoned |
- Type-ahead is a hazard here, not a convenience. Typing three letters and landing on whichever confusable name sorts first is exactly the failure this section is about. Where a list is long enough to need typing, it needs a search with visible results — not a jump.
- Never rely on the reader noticing. The clinician who picks the wrong one is not being careless; they are reading quickly under interruption, which is the specified context of use, not an aberration from it.
- A confusable pair is a finding for the risk file. If two options in a list can be mistaken, that is a use-related hazard under IEC 62366-1 with an identifiable mitigation, and it belongs in the file rather than in a designer's judgement.
What a dropdown must not grow into
Every feature below is offered by mainstream systems, each solves a real problem, and each turns the control into something other than a way of choosing one value from a known set.
| Feature | Verdict |
|---|---|
| A hyperlink among the options | No. One item that navigates away, among items that select, is a category error — and it abandons a partly-filled form to do it. If the reader needs to go somewhere, that is a control outside the list. |
| Multiple columns with sticky headers | No. That is a Data grid inside a transient surface, with none of a grid's semantics and none of its rules about truncation, sorting or keyboard traversal. |
| A filter field inside the menu | Only where the list genuinely cannot be shortened, and then Escape must be unambiguous — it closes the menu, and clearing the filter is its own control. A filter that narrows to a single option never selects it automatically. |
| Select-all in a multi-select | Only with the set named — "Select all 4 shown" or "Select all 19", per List. A bare "Select all" beside a filtered list is a coin toss. |
| Icons beside options | No. There is no icon set here, and an option is text a clinician must be able to read aloud to a colleague. |
| Options that change while it is open | No. A list reordering or re-populating under a pointer already moving is how the wrong option is chosen — the same objection as Reprioritisation. Changes wait for the menu to close. |
The pattern is the one from Menu: a transient surface accumulates features because it is convenient to put them there, and each addition costs a protection the surface was never designed to provide. A dropdown that needs a filter, columns and a link is a panel that has not admitted it.
Native or custom
| Use | When | Why |
|---|---|---|
Native <select> |
Simple lists, and all touch platforms | The OS picker is larger, better at one-handed use, works gloved, and needs no custom keyboard handling. It is almost always the safer control on a cart. |
| Custom listbox | Grouping, search, rich option content, multi-select | Only where native cannot express it. Accepts the full cost of implementing keyboard, focus and announcement correctly. |
A custom select that is merely prettier than the native one is not worth its accessibility surface. Reach for it when the requirement genuinely exceeds what native offers.
States and validation
- Unanswered is a state, not an error — until submission is attempted. Then it becomes an error naming the field and moving focus to it.
- Changing a selection never clears other fields. Where a change genuinely invalidates dependent input, say so and confirm before discarding anything typed.
- Loading options is visible. An empty list while a fetch is in flight is indistinguishable from a list with no options; show the pending state and, on failure, say the options could not be loaded rather than presenting an empty set.
- Read-only shows the chosen value at full contrast with no caret — it is information, not an unavailable control.
Unanswered looks unanswered. Nothing is recorded unless a clinician chooses it.
First option pre-selected. Every unattended form now records "Critical", and nobody chose it.
What the component enforces, and what it only asks for
A native <select>, deliberately — which means the platform holds more here than usual, including one behaviour this page argues against and cannot remove.
| Rule | Held by | How |
|---|---|---|
| Options are text, with no icon slot | The component | SelectOption is a value and a label. There is no way to put a glyph, a link or a nested control in the list.Asserted: Select: an option is a value and a label, with nowhere to put a glyph |
| The closed control restates the whole option | The component | A native select shows the selected option's full label, which is where a mis-selection is actually caught. Asserted: Select: the closed control carries the whole option, not a truncation |
| An error is added to the hint, never substituted | The component | Same wiring as Text field. Asserted: No component substitutes an error for its hint |
| Keyboard and screen-reader behaviour | The platform | Everything a native select does on every platform, including on a touch device where it becomes the system picker. |
| Type-ahead | The platform | Typing jumps to whichever option sorts first, and a native select cannot switch that off. This page argues type-ahead is a hazard among confusable names — that is an argument for not putting confusable names in a long select, not a behaviour the component removes. |
| Confusable options are separated and disambiguated | You | Grouping, ordering, Tall Man lettering and the strength-and-route suffix are all properties of the options array you build. |
| Nothing is pre-selected where the choice is clinical | You | defaultValue passes straight through. |
A rule stated on a component page reads as a rule the component keeps. 3 of these 7 are; 2 are obligations this page was quietly handing to the reader. The most important rule on this page — that two look-alike names are never adjacent and never distinguished by name alone — lives entirely in the data you pass.
Do's and don'ts
Unanswered looks unanswered. Nothing is recorded unless a clinician chooses it.
First option pre-selected. Every unattended form records “Critical”, chosen by nobody.
- Left ventricular hypertrophy
- Left ventricular hypertrophy with strain
- Other…
Full option text, wrapping if needed, plus an explicit “Other” where the list may be incomplete.
Truncated to the same visible string. Two different findings, indistinguishable at the point of choosing.
Vasopressors
Antiarrhythmics
Inotropes
The confusable pair is separated by grouping, and each option carries a strength and a route. Two things must go wrong, not one.
Alphabetical order has placed three similar names together, with nothing to tell them apart. The ordering created the hazard.
A long list gets a search with visible results — the reader sees what matched before choosing.
Typing narrows and shows. Nothing is selected by having landed on it.
Type-ahead: d-o-p jumps to whichever name sorts first.
Three keystrokes and a confusable name is selected without being read. This is the failure the section is about, automated.
The closed control restates the whole option. This is where a mis-selection actually gets caught.
Truncated back to almost the bare name, discarding the attribute that was added to tell it from its neighbour.
Accessibility
- Native first. The most accessible select is the one the platform provides.
- Custom listboxes implement the full pattern —
role="combobox"on the trigger witharia-expanded,role="listbox"androle="option"witharia-selected, arrow-key navigation, Home/End, type-ahead, Esc to close, and focus returning to the trigger. - Group headings use real semantics, so screen-reader users hear the grouping rather than a stray option.
- The listbox is reachable at 200 % zoom and repositions inside the viewport instead of clipping.
- Selection is not colour alone — the selected option carries a check as well as a fill.
- Touch targets: options obey the physical floor from Scaling, which usually means fewer visible at once on a cart. That is the correct trade.
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 recorded value is one the clinician actually chose. Silent defaults are the failure mode this component owns.
- Efficiency — the right option found without reading the whole list; clinical ordering and grouping do more for this than search does.
- Satisfaction — confidence that the form says what they meant, and no sense of having been answered on their behalf.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.
- No pre-selected clinical value. (you) Mitigates: a default recorded as a clinician's decision without being seen.
- Options never truncated. (the component) Mitigates: two similar findings being indistinguishable at the point of choosing.
- "Other" with free text where the list may be incomplete. (you) Mitigates: forced mis-classification into the nearest wrong option.
- Failed option loading is stated. (you) Mitigates: an empty list read as "no applicable options".
- Changing a selection never silently clears input. (you) Mitigates: loss of typed clinical text.
- Native control on touch. (the platform; it is a native select) Mitigates: mis-selection when gloved or one-handed.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/select.json
<Select
label="Infusion"
hint="Strength and route are part of the option"
placeholderOption="Choose an infusion"
options={[
{ value: "dobu", label: "DOBUTamine 250 mg/20 mL · infusion" },
{ value: "amio", label: "Amiodarone 300 mg/10 mL · IV bolus" },
{ value: "dopa", label: "DOPamine 200 mg/5 mL · infusion" },
]}
/>
// Options are flat data with no icon slot and no link item, so a dropdown
// cannot grow into a navigation surface.
What the component takes. This table used to list placeholder, clinical, searchable, optionsError — props the component does not have, and never had. Found by the registry gate’s props-table rule.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | — | Required. |
options | SelectOption[] | — | Required. Text only — there is no icon slot. |
hint | string | — | What the field is for. Never removed when an error appears. |
error | string | — | Added below the hint, and described first. |
placeholderOption | string | — | The unselected state, as a first option with no value. Pass it wherever the choice is clinical: without it the first option is pre-selected, and nobody chose it. |
Related
- Text field — labels, hints and validation behaviour.
- Clinician override — the reason list in context.
- Interaction states — disabled versus read-only.
- Scaling & displays — option target size.