← All mockups

Proposal · Searchable single select

Dropdown solution A — Dual-layer selected value

Restore avatars and start icons in the collapsed searchable select without regressing the accessibility fix documented in Vibe: the selected label must stay in the native input so screen readers never announce “blank.”

Interactive example

Try focus, type-to-filter, select, clear, and keyboard: ↓ ↑ Enter Esc. Focus keeps the avatar and shows a caret in the name. Full rich chrome hides when you type.

Choose a person. Type to search.

On focus: “Person, Julia Martinez, combobox”
Layer 1 · AT
input.value = "Julia Martinez"
Screen readers announce the real value. Meets WCAG 4.1.2.
Layer 2 · Visual
Avatar + label for sighted users (aria-hidden="true", pointer-events none). Never empty the input to show this chrome.

Static states

Visual stages for design review and QA. Same interaction rules as the live demo.

1 · Default (empty)

Person
Select a person

Placeholder only. Visible label remains (placeholder ≠ accessible name).

2 · Collapsed selected (rich)

Person
Julia Martinez

Avatar visible. value still = “Julia Martinez”.

3 · Focused (avatar + caret)

Person
Julia Martinez

Avatar stays; overlay label hides so real text + caret show beside it.

4 · Filtering (rich hidden)

Person
Jul

User is editing a query — show real input text, hide value chrome.

5 · Reopen with selection

Person
Julia Martinez

Full list on reopen (per current eng behavior). Selected option marked visually + aria-selected.

Why this works vs the documented problems

Each row maps to an issue or trade-off called out in the Vibe searchable single-select accessibility docs.

From Storybook / change notes How Solution A addresses it
SR announced “blank” when selection was only a visual overlay on an empty input (WCAG 4.1.2). Input is never emptied to show chrome. value stays the label. Overlay is decorative only (aria-hidden).
Core rule: selected value lives inside the input — AT reads the input, not layers around it. Dual-layer keeps that rule. Layer 2 is paint only; Layer 1 remains the source of truth.
Trade-off: collapsed value is text-only — lost startElement / avatar / endElement / searchable valueRenderer. Collapsed can look rich again. Visual mirror stays synced to the same label string AT reads.
Native input can only hold a string — so icons were dropped after the fix. True for the value. Icons live in a separate visual layer; they don’t need to be inside the string.
Focus must stay on the input after select; menu still closes. Same editable combobox model. Rich layer has pointer-events: none; focus never moves to the overlay.
helperText tied via aria-describedby (1.3.1). Unchanged — dual-layer does not replace labeling or description wiring.
Combobox must advertise the popup it controls. Keep aria-haspopup, aria-expanded, aria-controls on the combobox input.
Type to filter / clear text restores full list; reopen shows full list with selection marked. On type: hide rich layer, show filter string. Behavior matches the docs’ interaction table.
Placeholder is not a label; clearAriaLabel required when clearable. Still required. Dual-layer does not change naming props — visible label + named clear button.
Key distinction Old (broken): rich overlay + empty input → blank.
Current docs: filled input → correct AT, text-only collapsed UI.
Solution A: rich overlay + filled input → icons for sighted users, value for AT.

Technical handoff

Implementation checklist for Dropdown / searchable single select. Prefer this pattern when filtering stays in the trigger field (editable combobox).

Invariants (do not regress)

  1. When an option is selected, set input.value to the option’s label string. Never clear value just to show rich chrome.
  2. Keep focus on the combobox input after select; close the menu (current behavior).
  3. Preserve aria-expanded, aria-controls, aria-haspopup, aria-activedescendant (or equivalent APG wiring).
  4. Keep label / aria-labelledby, helperText + aria-describedby, clearAriaLabel, noOptionsMessage.

Dual-layer behavior

  1. When selected and not filtering: show decorative rich layer (startElement / avatar / safe valueRenderer chrome with aria-hidden="true" and pointer-events: none.
  2. Visually hide input text while collapsed (e.g. transparent text) — do not remove value.
  3. On focus with a selection: keep the avatar (or start icon); hide the overlay label so the real input text and caret show beside it (avoids caret-through-overlay).
  4. On type / filter: hide the full rich layer; input shows the query string; list filters as today.
  5. On select, or blur with a selection and not filtering: restore rich layer; keep focus on input.
  6. Sync rule: rich chrome must always reflect the same option whose label is in input.value. If text ≠ selected label, you are filtering — hide chrome.

Forced colors / high contrast

Storybook / docs update

QA

Recommendation Ship Dropdown solution A (dual-layer) for type-in-field searchable single select when product needs avatars / start icons in the collapsed state. Do not revert to empty-input overlays.