Dropdown

How the Dropdown works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.

AAWCAG 2.2 target
17/21contrast pairs passing
8keyboard interactions

The Dropdown follows the WAI-ARIA select-only combobox pattern: a button with role="combobox" that opens a listbox. Focus stays on the trigger throughout, and aria-activedescendant tells assistive tech which option is active.

Keyboard#

Tab

Moves focus to the trigger. From an open menu, closes it and moves on.

↓

Opens the menu. While open, moves to the next option, skipping disabled ones.

↑

Opens the menu. While open, moves to the previous option.

Enter

Opens the menu. While open, chooses the active option and closes it.

Space

Does the same as Enter.

Home

While open, moves to the first option. End moves to the last.

A–Z

While open, moves to the next option starting with the letters typed.

Escape

Closes the menu without changing the value.

Semantics#

role="combobox"

On a <button>, with aria-haspopup="listbox". Announced with its label and current value.

Trigger
aria-expanded

True while the menu is open.

Trigger
aria-controls

Points at the listbox.

Trigger
aria-activedescendant

The id of the active option, so it is announced while focus stays on the trigger.

Open
aria-labelledby

The label and the trigger itself, so it is read as Role, Designer.

Trigger
role="listbox" / "option"

Each row is an option, with aria-selected on the chosen one and aria-disabled on unavailable ones.

Menu
aria-invalid, aria-describedby

As for the Input.

Hint or error
disabled

The trigger leaves the tab order and the menu does not open.

Disabled

Colour contrast#

Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind it.

4.5:1

Value and options · Trigger, Menu

The value, option labels and chevron in #2a2a2a, and descriptions in #6d6d6d, on white and the 4% active-row wash. All pass. The option badge is #005cf8 on #ebf2ff and passes.

WCAG 1.4.3 Contrast (Minimum) ↗
4.5:1

Placeholder · Empty

#8f8f8f, 3.23:1 at rest. It darkens to #2a2a2a while the trigger has focus or the menu is open.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1

Trigger boundary · Default, Hover

The 1px #e1e1e1 stroke, 1.31:1. The chevron (#2a2a2a) also shows it is a control, which helps.

WCAG 1.4.11 Non-text Contrast ↗
3:1

Active option · Open

The active row is a 4% wash with a #f2f2f2 hairline, far under 3:1 against the menu. A keyboard user may not see which row is active; the chosen row's tick is #005cf8 and passes.

WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗
3:1

Focus indicator · Focus visible

A 2px ring in #1a6ef8 off a white gap, on keyboard focus only. It measures 4.54:1 on white, so it clears 3:1 on every light surface the library uses.

WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗
Not required

Disabled · Disabled

Inactive controls are exempt. The disabled text and glyphs (#cbcbcb) are deliberately faint.

WCAG 1.4.3 Contrast (Minimum) ↗

Measured contrast#

The trigger against its white fill and hover wash, and each option against the white menu and the active row's 4% wash, from the ds.css tokens; text is held to 4.5:1 and the stroke and chevron to 3:1. 4 of 21 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.

Part · elementRestHoverOpen or active
Trigger · value14.35passes13.17passes14.35passes
Trigger · placeholder3.23below the minimum2.97below the minimum14.35passes
Trigger · stroke1.31below the minimum1.20below the minimum20.14passes
Trigger · chevron14.35passes13.17passes14.35passes
Option · label14.35passes13.17passes13.17passes
Option · description5.17passes4.75passes4.75passes
Option · badge4.81passes4.81passes4.81passes

Motion#

  • The trigger's fill and stroke change over 150ms, and the chevron turns over 200ms when the menu opens. Both are turned off under prefers-reduced-motion.
  • The menu appears without a transition.

Checklist#

Give the dropdown a visible label; the listbox is named by it too, so without one the menu has no name.

Home, End and typeahead work only while the menu is open. Alt + ↓ is not handled; ↓ alone opens it.

The active row's wash is too faint to see from the keyboard. It needs a stronger fill or a visible edge at 3:1 before the code library ships.

Placeholder (#8f8f8f, 3.23:1) and the resting stroke (#e1e1e1, 1.31:1) fall under their minimums, as on the Input.

Disabled options stay in the list and are announced as unavailable. Say why in the description where it is not obvious.

For lists people search, use an editable combobox with filtering instead; this one has typeahead but no text field.