What's new Report an issue
GIG Cymru NHS Wales, Digital Health and Care Wales

Select

One value from a list too long to show all at once — roughly seven to fifty options. Below seven, use a Radio group; above fifty, the user is searching, not choosing.

Which one

ControlAnswersTakes effect
CheckboxAny number, including noneOn Save
RadioExactly one, all options visibleOn Save
SelectExactly one, from a longer listOn Save
SwitchOn or offImmediately

The field

This is a button and a listbox, not a native <select>. That is what makes the menu stylable and consistent across browsers — and it is why every keyboard behaviour is ours rather than the platform's. Shown open.

The ward the patient is being admitted to.

Aneurin ward
Tawe ward
Cynon ward
<div class="sr-select">
  <label class="sr-select__label" for="ward">Ward</label>
  <div class="sr-select__control">
    <button type="button" class="sr-select__trigger" id="ward" aria-haspopup="listbox" aria-expanded="false" data-placeholder="true">
      <span class="sr-select__value">Select a ward</span>
    </button>
    <div class="sr-select__menu" role="listbox" hidden>
      <div class="sr-select__option" role="option" aria-selected="false">Aneurin ward</div>
    </div>
  </div>
</div>

States

The placeholder is not a value. "Select a ward" must never be submittable, and it stays in secondary text until a real choice is made.

Select a ward to continue

<!-- data-placeholder="true" is what keeps "Select a ward" in secondary
     text. Remove it when a real value is set, or the value renders as
     though it were still a placeholder. -->
<div class="sr-select">
  <label class="sr-select__label" for="s1">Placeholder</label>
  <div class="sr-select__control">
    <button type="button" class="sr-select__trigger" id="s1" aria-haspopup="listbox" aria-expanded="false" data-placeholder="true">
      <span class="sr-select__value">Select a ward</span>
    </button>
  </div>
</div>

<div class="sr-select sr-select--error">
  <label class="sr-select__label" for="s2">Error <span class="sr-select__required">*</span></label>
  <div class="sr-select__control">
    <button type="button" class="sr-select__trigger" id="s2" aria-haspopup="listbox" aria-expanded="false" aria-describedby="s2-err" data-placeholder="true">
      <span class="sr-select__value">Select a ward</span>
    </button>
  </div>
  <p class="sr-select__error" id="s2-err">Select a ward to continue</p>
</div>

<div class="sr-select">
  <label class="sr-select__label" for="s3">Disabled</label>
  <div class="sr-select__control">
    <button type="button" class="sr-select__trigger" id="s3" disabled>
      <span class="sr-select__value">Aneurin ward</span>
    </button>
  </div>
</div>

One value from a list too long to show all at once.


When to use

When not to use

, a select hides the options behind a click, and comparing them costs the user an extra step for no gain.

How it works

Do & don't

DoDon't
Write the label as what is being chosen, "Ward"Write it as an instruction, "Please choose a ward"
Keep the placeholder unselectableLet "Select a ward" submit as a value
Put frequent options at the topOrder by internal code
Group a long list under headingsLet the menu grow past a screenful
Use Autocomplete once the list is searchableBolt a search box onto a select

Accessibility

Known gaps

Accessibility requirements

RequirementWCAG SCHow Single Record meets itTest method
The trigger exposes its role and state4.1.2A real button with aria-haspopup and aria-expanded; the menu is role="listbox" with role="option" children carrying aria-selected.Screen reader announce, open and close
Label is programmatically associated1.3.1A real label bound to the trigger, so the field announces as "Ward, collapsed" rather than as a bare button.Screen reader announce
Fully keyboard operable2.1.1Enter, Space or Down to open; Up/Down to move; Home/End to jump; typeahead to skip; Enter to choose; Escape to close and return focus to the trigger.Keyboard only
Focus does not escape an open menu2.4.3Focus stays within the listbox while open and returns to the trigger on close, so the tab order never lands behind the menu.Keyboard tab with menu open
Open state is not colour alone1.4.1The chevron rotates 180°, in addition to the focus ring and the menu appearing.Greyscale review
Error is exposed and actionable3.3.1, 3.3.3The field is marked invalid, the message is referenced from it, and it says what to do.Screen reader, submit empty
Focus visible and not clipped2.4.7A 3px Cyan/700 outer stroke drawn outside the field box, so the field border never crops it.Keyboard tab
Target size2.5.840px trigger and 40px options clear the 24px minimum outright; mobile layouts take the full 44px.Measure, touch device
Menu length stays reachable2.4.3The menu caps at 280px and scrolls; past that the list is grouped rather than made taller.Keyboard, long list