Skip to content

fix(a11y): always give DrawerContent an accessible DialogTitle#2451

Open
innolope-dev wants to merge 3 commits into
mainfrom
fix/dialog-title-a11y
Open

fix(a11y): always give DrawerContent an accessible DialogTitle#2451
innolope-dev wants to merge 3 commits into
mainfrom
fix/dialog-title-a11y

Conversation

@innolope-dev

@innolope-dev innolope-dev commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Problem

Sentry issue PEANUT-UI-MJS: DialogContent requires a DialogTitle for the component to be accessible for screen reader users. — 1,703 events in 14 days across 854 users, culprit page /home.

This is Radix Dialog's dev-mode accessibility warning (our Drawer wrapper is built on vaul, which is built on Radix Dialog). Any DrawerContent rendered without a DrawerTitle logs it to the console on every open, and the captureConsole Sentry integration amplifies it into an event per occurrence. It is also a real accessibility defect: screen readers announce the dialog with no name.

The one instance currently missing a title (BadgeStatusDrawer, opened from the badge strip on /home) is fixed in #2448 with a visible DrawerTitle (the badge name), which is the better a11y outcome there. This PR ships the reusable mechanism and the guardrail so the class of bug can't quietly come back; it no longer touches BadgeStatusDrawer and shares no files with #2448 — the two PRs can merge in any order.

Fix

  • DrawerContent (src/components/Global/Drawer) now accepts an accessibleTitle prop that renders a visually hidden (sr-only) DrawerTitle, for drawers whose design has no visible heading — same idiom already used manually in KycStatusDrawer, TransactionDetailsDrawer, and TokenSelector, now available as a one-prop path.
  • Regression tests pin the labelling contract: accessibleTitle produces a DialogTitle wired to the dialog's aria-labelledby, an explicit DrawerTitle child still labels the dialog, and the Radix missing-DialogTitle warning does not fire when the prop is used.
  • No warning suppression anywhere.

Audited all DrawerContent usages (ContributorsDrawer, CardUnlockDrawer, KycStatusDrawer, TransactionDetailsDrawer, ChooseNetworkDrawer, TokenSelector, QRBottomDrawer, dev DS pages): with #2448, all render a DrawerTitle. The headlessui-based Modal is unaffected (different library, no Radix warning).

Impact

  • Prevents recurrence of the PEANUT-UI-MJS class of bug: future drawers without a visible heading have a one-prop path to a proper accessible name, and the new tests guard the DialogTitle contract on the shared wrapper. (The instance fix that removes the current ~1.7k events / 14 days ships in fix(sentry): stop double-counting fetch failures, add missing DrawerTitle #2448.)
  • Screen-reader labelling behaviour of the shared drawer is now under test.

Testing

  • New src/components/Global/Drawer/__tests__/Drawer.test.tsx: asserts accessibleTitle renders an sr-only title wired to the dialog's aria-labelledby, that an explicit DrawerTitle child still labels the dialog, and that the Radix missing-DialogTitle console warning does not fire — 3/3 pass (re-verified after scoping out the BadgeStatusDrawer change; the suite is Drawer-generic).
  • pnpm tsc --noEmit clean (0 errors); prettier clean on touched files.

Radix Dialog (via vaul) logs 'DialogContent requires a DialogTitle' for
screen-reader accessibility when a drawer opens without a title. The badge
unlock drawer on /home rendered no DrawerTitle, and captureConsole forwarded
the warning to Sentry (PEANUT-UI-MJS, ~1.7k events / 854 users in 14 days).

Add an accessibleTitle prop to DrawerContent that renders a visually hidden
DrawerTitle, use it in BadgeStatusDrawer, and cover the labelling contract
with tests.
@innolope-dev innolope-dev self-assigned this Jul 18, 2026
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 18, 2026 11:07am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

DrawerContent now supports accessible titles through visually hidden DrawerTitle elements, with tests covering ARIA wiring and Radix warnings. BadgeStatusDrawer supplies a computed badge-specific title.

Changes

Drawer accessibility

Layer / File(s) Summary
Add accessible DrawerContent titles
src/components/Global/Drawer/index.tsx, src/components/Global/Drawer/__tests__/Drawer.test.tsx
DrawerContent accepts accessibleTitle, renders it as a visually hidden DrawerTitle, and tests aria-labelledby behavior and warning suppression.
Apply title to badge drawer
src/components/Badges/BadgeStatusDrawer.tsx
BadgeStatusDrawer passes Badge unlocked: ${displayName} as the drawer’s accessible title.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: 0xkkonrad

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the DrawerContent accessibility fix and the new DialogTitle behavior.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dialog-title-a11y

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6208.86 → 6209.32 (+0.46)
Findings: 0 net (+0 new, -0 resolved)

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2003 ran, 0 failed, 0 skipped, 25.1s

📊 Coverage (unit)

metric %
statements 59.5%
branches 43.3%
functions 48.5%
lines 59.8%
⏱ 10 slowest test cases
time test
3.0s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.9s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.2s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid Italian IBAN
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ETH address with surrounding spaces
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/Badges/BadgeStatusDrawer.tsx (1)

40-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the badge-level accessible name.

The shared drawer tests verify accessibleTitle, but add a BadgeStatusDrawer test that opens the drawer and asserts the dialog name is Badge unlocked: ${displayName}. This protects the caller’s interpolation and wiring from future regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Badges/BadgeStatusDrawer.tsx` at line 40, Add a regression
test for BadgeStatusDrawer that opens the drawer with a known displayName and
asserts the dialog’s accessible name is interpolated as “Badge unlocked:
${displayName}”. Exercise the caller wiring rather than relying only on shared
Drawer tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Global/Drawer/__tests__/Drawer.test.tsx`:
- Around line 50-64: Update the test case “does not trigger the Radix
missing-DialogTitle warning when accessibleTitle is set” so the console.error
spy is always restored, including when rendering or assertions fail. Wrap the
render and assertions in a try/finally that calls errorSpy.mockRestore(), or use
an equivalent afterEach cleanup.

---

Nitpick comments:
In `@src/components/Badges/BadgeStatusDrawer.tsx`:
- Line 40: Add a regression test for BadgeStatusDrawer that opens the drawer
with a known displayName and asserts the dialog’s accessible name is
interpolated as “Badge unlocked: ${displayName}”. Exercise the caller wiring
rather than relying only on shared Drawer tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2513920d-8b3a-4cbe-91b8-3fb75af9bcb0

📥 Commits

Reviewing files that changed from the base of the PR and between f9ab02d and 00c97cc.

📒 Files selected for processing (3)
  • src/components/Badges/BadgeStatusDrawer.tsx
  • src/components/Global/Drawer/__tests__/Drawer.test.tsx
  • src/components/Global/Drawer/index.tsx

Comment thread src/components/Global/Drawer/__tests__/Drawer.test.tsx
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