feat(ui): custom accessible listbox Select#5
Merged
Conversation
The native <select> open option list is rendered by the OS and cannot be styled, so it broke the design system when expanded (system font, blue highlight, square corners). Replace it with a custom WAI-ARIA select-only combobox that we fully control. - New Select API: `value` + `onValueChange` + `options` (no <option> children). - WAI-ARIA select-only combobox pattern: role="combobox" trigger with aria-haspopup/expanded/controls/activedescendant; role="listbox"/"option" popup; focus stays on the trigger and aria-activedescendant tracks the highlight. - Keyboard: Up/Down, Home/End, Enter/Space to open/select, Escape to close, Tab to dismiss, printable-char typeahead. Outside-pointer-down closes. - Styled open list: token surface, emerald highlight, selected checkmark, chevron rotates on open. - Navigation rules extracted to a pure, tested helper (src/lib/selectNavigation.ts) + tests; total suite 26 -> 34. - Editor's Interaction/Tool selects migrated to the new API and labelled via aria-labelledby. Docs: CLAUDE.md documents the design-token system and the custom Select contract so future work uses the options API, not native <option> children. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review feedback: the typeahead reset setTimeout was cleared on the next keystroke but never on unmount. Add an unmount-cleanup effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DCCA
pushed a commit
that referenced
this pull request
Jun 22, 2026
PRs #3-#5 (UI refactor) and a new CLAUDE.md landed on main without Prettier formatting, so the format:check gate added in the hardening sweep failed on main. Format the 4 offending files (CLAUDE.md, badge.tsx, editor/main.tsx, viewer/main.tsx). No logic changes. All gates pass locally: typecheck, lint, format:check, test (34), build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5c2e5VcKVhugy7TAzi4W
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The native
<select>open option list is OS-rendered and unstylable, so it broke the design system when expanded (system font, blue highlight, square corners). This replaces it with a custom WAI-ARIA select-only combobox we fully control.value+onValueChange+options(no more<option>children).role="combobox"trigger (aria-haspopup/expanded/controls/activedescendant) +role="listbox"/"option"popup. Focus stays on the trigger;aria-activedescendanttracks the highlight. Labelled viaaria-labelledbyfrom the visible field labels.src/lib/selectNavigation.ts) with tests. Suite 26 → 34.Docs
CLAUDE.mdupdated: documents the design-token system and the customSelectcontract (use the options API, not native<option>), and addsselectNavigation.tsto the pure-helpers list.Verification
npm run check— typecheck + lint + 34 tests + build all greenKnown limitation
The popup is positioned inside the editor sidebar card (
overflow-auto). For the two short, top-of-card selects it opens cleanly in view; a Select placed low in a scrolling panel could clip and would need a portal. Not an issue for current placement.🤖 Generated with Claude Code