♿️ fix: trap focus in Modal and Drawer and fix menu semantics - #672
Merged
Conversation
futjesus
force-pushed
the
ci/pr-checks-and-coverage
branch
from
July 27, 2026 14:52
9c8f505 to
1d5e16b
Compare
futjesus
force-pushed
the
fix/a11y-focus-and-roles
branch
from
July 27, 2026 14:58
4620077 to
605bd9a
Compare
- Wrap Modal and Drawer panels in FocusLock (react-focus-lock, already a dependency): focus moves in on open, Tab is trapped, focus is restored on close - Rewrite DropdownButton on @radix-ui/react-dropdown-menu: keyboard navigation, aria-haspopup/expanded and Escape/outside-click handling come from Radix; the hardcoded trigger text is now a label prop with the old default - Loading exposes role=status with an overridable accessible name - ProgressBar exposes role=progressbar with aria-valuenow/min/max, an optional ariaLabel and the status as visually hidden text - Autocomplete options are a single interactive element instead of a button nested inside role=option; keyboard Enter/Space activates the option
futjesus
force-pushed
the
fix/a11y-focus-and-roles
branch
from
July 27, 2026 15:03
605bd9a to
1ba36c2
Compare
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
Third PR of the audit series (stacked on #671). Fixes the accessibility issues found in the audit, with tests for each.
Changes
Focus management
FocusLock(react-focus-lockwas already a dependency, previously unused): focus moves into the dialog on open, Tab/Shift+Tab are trapped inside, and focus returns to the trigger on close. The wrapper usesclassName="contents"so it doesn't affect grid/positioned layout.useModal's manualcloseBtnRefrestore was left untouched — it isn't wired into the library's own render path and removing it could affect consumers using the hook directly.DropdownButton rewritten on Radix DropdownMenu (new dep:
@radix-ui/react-dropdown-menu)The previous hand-rolled menu was unusable by keyboard or screen reader (
<li onClick>without role/tabIndex/key handlers, trigger withoutaria-haspopup/aria-expanded). Radix provides roving focus, typeahead, ARIA wiring and Escape/outside-click for free. Public props are unchanged; the hardcoded "Download Invoice as" trigger text is now alabelprop that defaults to the old string (flagged for removal later). Content renders without a Portal solistClassNameconsumers keep the same DOM position; width matches the trigger via Radix's CSS var.Semantics
role="status"+aria-label="Loading"(overridable via delegated props) — previously invisible to assistive tech.role="progressbar"witharia-valuenow/min/max(clamped), optionalariaLabelprop, and thestatusannounced as visually-hidden text instead of color-only.buttonnested insiderole="option"; the option itself is now the single interactive element, and keyboard Enter/Space activates it via the navigation hook.Testing