Components · Forms

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.

Stable · v1.0 shadcn/ui WCAG 2.2 AA

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

Closed · unanswered, and open
Optional. Recorded with your assessment.
  • Early repolarisation
  • Pericarditis
  • Left ventricular hypertrophy
  • Artefact
  • Lead misplacement
ElementRule
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.
Never pre-select a clinical value

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

Native or custom

UseWhenWhy
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

Do

Unanswered looks unanswered. Nothing is recorded unless a clinician chooses it.

Don't

First option pre-selected. Every unattended form now records "Critical", and nobody chose it.

Do's and don'ts

Do

Unanswered looks unanswered. Nothing is recorded unless a clinician chooses it.

Don't

First option pre-selected. Every unattended form records “Critical”, chosen by nobody.

Do
  • 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.

Don't
  • Left ventricular hypertrophy
  • Left ventricular hypertrophy with strain

Truncated to the same visible string. Two different findings, indistinguishable at the point of choosing.

Accessibility

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.

Clinical safety notes

Risk controls carried by this component

Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).

Implementation

$ npx shadcn@latest add https://md.notjustany.tech/r/select.json
<Select
  label="Reason for override"
  placeholder="Select a reason"   // required — there is no default value
  hint="Optional. Recorded with your assessment."
  clinical                          // forbids defaultValue; forces native on touch
  onValueChange={setReason}
>
  <SelectGroup label="Non-ischaemic ST elevation">
    <SelectItem value="early-repol">Early repolarisation</SelectItem>
    <SelectItem value="pericarditis">Pericarditis</SelectItem>
  </SelectGroup>
  <SelectOther />                  // "Other" + free text
</Select>

// Enforced, not documented.
if (clinical && defaultValue !== undefined) {
  throw new Error(
    "[Select] `clinical` selects may not carry a defaultValue. " +
    "A value nobody chose must not be recorded as a decision."
  )
}
PropTypeDefaultNotes
labelstringRequired.
placeholderstring Required. The unanswered state must read as unanswered.
clinicalbooleanfalse Forbids defaultValue; renders native on touch platforms.
searchablebooleanauto above 15 options Filters, never hides — the full list stays reachable by clearing the query.
optionsErrorstring Renders instead of an empty list when loading failed.
NotJustAnyMed.Tech Design System · Select · v1.0 · draft for review
Reference applications named in this system are fictional; all patient data shown is fabricated.