Skip to content

fix: add dev warning when dialog has no accessible title#9819

Merged
snowystinger merged 8 commits intoadobe:mainfrom
mvanhorn:fix/dialog-title-dev-warning
Apr 1, 2026
Merged

fix: add dev warning when dialog has no accessible title#9819
snowystinger merged 8 commits intoadobe:mainfrom
mvanhorn:fix/dialog-title-dev-warning

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Summary

  • Adds a development-mode warning when a dialog is rendered without an accessible title (no aria-label, aria-labelledby, or visible title element with titleProps)
  • Warns once per dialog instance to avoid console spam on re-renders
  • Warning is gated behind process.env.NODE_ENV !== 'production' and tree-shaken in production builds

Closes #5402

Context

When useDialog and useOverlayTriggerState are used in the same component, useSlotId may return undefined because the title element is not in the DOM when the slot query runs. This silently produces an inaccessible dialog with no aria-labelledby. The new warning catches this common mistake and tells the developer exactly how to fix it.

Test plan

  • New test: warning fires when dialog has no accessible title
  • New test: no warning when aria-label is provided
  • New test: no warning when aria-labelledby is provided
  • New test: no warning when a title element is rendered with titleProps
  • Existing tests updated to provide aria-label (they were previously untitled)
  • yarn jest packages/react-aria/test/dialog/useDialog.test.js - 8/8 passing

This contribution was developed with AI assistance (Claude Code).

When a dialog is rendered without an aria-label, aria-labelledby, or a
visible title element, emit a console.warn in development mode to help
developers catch this common accessibility mistake early. The warning
fires once per dialog instance and is tree-shaken in production builds.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Check the DOM element directly for aria-label/aria-labelledby attributes
instead of relying on hook props, since wrapper components (e.g. RAC Dialog)
may add aria-labelledby from context after useDialog runs.

Add the warning pattern to the allowed warnings list in setupTests.js so
existing tests that render dialogs without accessible labels are not broken.
Copy link
Copy Markdown
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

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

thanks for the pr

@snowystinger snowystinger mentioned this pull request Mar 30, 2026
5 tasks
…p 'visible' from warning

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@snowystinger
Copy link
Copy Markdown
Member

Looks like there's a number of failing tests because we need to add titles to all of them. That's probably why claude was trying to hide them all, better to fix something this simple upfront though. Do you want to do that? otherwise one of us can

Adds aria-label="Test dialog" to Dialog test renders that don't
have a heading or aria-labelledby, preventing the new dev warning
from failing tests via failTestOnConsoleWarn().

9 test files fixed. Remaining failures (ContextualHelp, DatePicker,
DateRangePicker, Form, ListView) have dialogs rendered indirectly
through components — those need source-level changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@mvanhorn
Copy link
Copy Markdown
Contributor Author

Pushed 195a5f7 — added aria-label to test dialogs that lack accessible titles across 9 test files (Dialog, DialogTrigger, Popover, Tray, Menu, ActionGroup, useHover, usePress, aria-modal-polyfill).

The remaining 5 failures (ContextualHelp, DatePicker, DateRangePicker, Form, ListView) render dialogs indirectly through components. Those will need source-level changes to pass the aria-label through. Working on those next.

Matt Van Horn and others added 2 commits March 30, 2026 14:27
… warning

The useEffect-based DOM check can race with useSlotId registration,
causing false positives in components where a heading exists but
hasn't registered yet.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…itleId check

ContextualHelp renders <Header> (not <Heading>), so the Dialog's
useSlotId resolves to undefined and the dialog has no accessible
title. Pass the variant label ("Help"/"Information") as aria-label.

Reverts the !!titleId check in useDialog which didn't work because
useSlotId resolves to undefined via useLayoutEffect before the
warning's useEffect runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@mvanhorn
Copy link
Copy Markdown
Contributor Author

Fixed in 3bd3697. The remaining test failures broke down as:

Our regressions (now fixed):

  • ContextualHelp.test.js and Form.test.js — ContextualHelp renders <Header> (maps to the header slot), not <Heading> (maps to the heading slot that receives titleProps). So the Dialog had no accessible title. Added aria-label={labelProps['aria-label']} to the Dialog inside ContextualHelp, which passes the variant label ("Help" or "Information") through.
  • Also reverted an intermediate commit that tried checking titleId in the warning — that didn't work because useSlotId resolves to undefined via useLayoutEffect before the warning's useEffect runs.

Pre-existing on main (not caused by this PR):

  • ListView.test.js (toBeEmptyDOMElement assertion)
  • DatePicker.test.js (era segment test)
  • DateRangePicker.test.js (range description assertion)
  • NumberParser.test.js (Swiss currency parsing)

Full suite now shows zero dialog-related warnings.

Copy link
Copy Markdown
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

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

We had a typo in our tests using the wrong component. It looks like the AI didn't consult our docs, because, according to them, it should be Heading https://react-spectrum.adobe.com/v3/ContextualHelp.html

I've gone ahead and updated the tests and removed the change to the ContextualHelp component.

@mvanhorn
Copy link
Copy Markdown
Contributor Author

Good catch on the Heading vs Header distinction - I should have checked the component docs before assuming the slot mapping. Thanks for fixingg the ContextualHelp tests directly.

@snowystinger snowystinger added this pull request to the merge queue Apr 1, 2026
Merged via the queue into adobe:main with commit ac718c6 Apr 1, 2026
30 checks passed
@mvanhorn
Copy link
Copy Markdown
Contributor Author

mvanhorn commented Apr 4, 2026

Thanks for the review and merge! Good to know the warning approach was right.

@mvanhorn
Copy link
Copy Markdown
Contributor Author

mvanhorn commented Apr 7, 2026

Thanks for merging, @snowystinger.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No title id is returned when useDialog and useOverlayTriggerState are used in the same component

3 participants