Proposal · Searchable single select
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.”
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.
input.value = "Julia Martinez"aria-hidden="true",
pointer-events none). Never empty the input to show this chrome.
Visual stages for design review and QA. Same interaction rules as the live demo.
Placeholder only. Visible label remains (placeholder ≠ accessible name).
Avatar visible. value still = “Julia Martinez”.
Avatar stays; overlay label hides so real text + caret show beside it.
User is editing a query — show real input text, hide value chrome.
Full list on reopen (per current eng behavior). Selected option marked visually + aria-selected.
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.
|
Implementation checklist for Dropdown / searchable single select. Prefer this pattern when filtering stays in the trigger field (editable combobox).
input.value to the option’s label string. Never clear value just to show rich chrome.aria-expanded, aria-controls, aria-haspopup, aria-activedescendant (or equivalent APG wiring).label / aria-labelledby, helperText + aria-describedby, clearAriaLabel, noOptionsMessage.startElement / avatar / safe valueRenderer chrome with
aria-hidden="true" and pointer-events: none.
value.
input.value.
If text ≠ selected label, you are filtering — hide chrome.
forced-colors: active, prefer showing the native input value and hiding or simplifying the overlay so the value remains visible.noOptionsMessage.