Skip to content

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

Open
mvanhorn wants to merge 6 commits intoadobe:mainfrom
mvanhorn:fix/dialog-title-dev-warning
Open

fix: add dev warning when dialog has no accessible title#9819
mvanhorn wants to merge 6 commits intoadobe:mainfrom
mvanhorn:fix/dialog-title-dev-warning

Conversation

@mvanhorn
Copy link
Copy Markdown

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 <noreply@anthropic.com>
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

const WARNING_PATTERNS_WE_SHOULD_FIX_BUT_ALLOW = [
'Browserslist: caniuse-lite is outdated'
'Browserslist: caniuse-lite is outdated',
'A dialog must have a visible title for accessibility'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we should not have this turned on. do you need it? I looks like you're already catching the warning with a jest spy/mock

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed in 7a62c3b. The jest spy catches the warning in the test directly, so the allowlist entry was unnecessary.

let hasAriaLabelledby = el.hasAttribute('aria-labelledby');
if (!hasAriaLabel && !hasAriaLabelledby) {
console.warn(
'A dialog must have a visible title for accessibility. ' +
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
'A dialog must have a visible title for accessibility. ' +
'A dialog must have a title for accessibility. ' +

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied in 7a62c3b.

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@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) <noreply@anthropic.com>
@mvanhorn
Copy link
Copy Markdown
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) <noreply@anthropic.com>
…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) <noreply@anthropic.com>
@mvanhorn
Copy link
Copy Markdown
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.

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.

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

2 participants