Skip to content

fix(ui): expose Drawer as an accessible modal dialog#6643

Merged
otavio merged 1 commit into
masterfrom
fix/drawer-dialog-a11y
Jul 13, 2026
Merged

fix(ui): expose Drawer as an accessible modal dialog#6643
otavio merged 1 commit into
masterfrom
fix/drawer-dialog-a11y

Conversation

@otavio

@otavio otavio commented Jul 13, 2026

Copy link
Copy Markdown
Member

What

The base Drawer component now exposes its panel as an accessible modal dialog — role="dialog", aria-modal="true", and an accessible name via aria-labelledby. This fixes accessibility for all 15+ drawers at once (MFA, connect, firewall rules, SSH keys, namespace edits, …).

Why

Drawer.tsx rendered the panel as a plain <div>, so assistive tech saw no dialog role, no modal boundary, and no accessible name tying the panel to its title. Focus trapping and Escape-to-close were already implemented (useFocusTrap / useEscapeKey) — only the ARIA layer was missing.

Fixes shellhub-io/team#128

Changes

  • Drawer.tsx: added role="dialog", aria-modal="true", and aria-labelledby on the panel; added an id on the <h2> title. The id comes from React's useId() rather than a fixed string — closed drawers stay mounted behind inert, so a literal id would produce duplicate-id collisions and an ambiguous aria-labelledby when more than one drawer is in the DOM.
  • Drawer.test.tsx (new): asserts the panel is reachable as role="dialog" with the title as its accessible name, and that aria-labelledby resolves to the heading's id.

Testing

npm run test (console): 2622 pass, incl. the new Drawer test; npm run build (tsc) clean; eslint clean on the changed files. The two remaining eslint warnings are pre-existing, on the backdrop overlay div's click handler — unrelated to this change.

The base Drawer panel rendered as a plain <div>, so assistive tech saw no
dialog role, no modal boundary, and no accessible name — affecting all 15+
drawers (MFA, connect, firewall rules, SSH keys, namespace edits, ...).

Add role="dialog", aria-modal="true", and aria-labelledby pointing at the
title heading via a useId()-generated id (a stable literal id would collide
since closed drawers stay mounted behind inert). The focus trap and Escape
handling were already wired via useFocusTrap/useEscapeKey.

Fixes: shellhub-io/team#128
@otavio
otavio requested a review from a team as a code owner July 13, 2026 19:44
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Claude finished @otavio's task in 46s —— View job


Code Review Complete

Reviewed 2 files across code quality, security, testing, language patterns,
and architecture — no new issues found. The code looks good as-is.

The useId() approach is the correct call here: since closed drawers remain mounted behind inert, a literal id would collide across the 15+ drawer instances and break aria-labelledby resolution. The panel is properly excluded from assistive tech when closed via aria-hidden/inert, and the new tests cover both the role="dialog" accessible name and the heading-id association.


To request another review round, comment /review.

@otavio
otavio enabled auto-merge (rebase) July 13, 2026 19:45
@otavio
otavio merged commit 8ea447b into master Jul 13, 2026
14 checks passed
@otavio
otavio deleted the fix/drawer-dialog-a11y branch July 13, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant