( Robust / WCAG 4.1.2 )
Dialog or modal has no accessible name
What is this issue?
A dialog (an element with role="dialog" or role="alertdialog", or the native <dialog> element) has no accessible name, the text a screen reader announces to identify an element. It comes from the first source that applies, in order: aria-labelledby, aria-label, visible label text, then other fallbacks specific to the element type.
The role itself still announces correctly the instant focus lands inside the dialog: a screen reader does say “dialog.” What’s missing is any text tied to that role describing which dialog it is, because neither aria-labelledby nor aria-label resolves to anything on the dialog’s own container element.
Why does this matter?
Every dialog interrupts whatever the user was doing to demand attention right now. If the only thing a screen reader can say about that interruption is “dialog,” the user is left to explore its contents before they can even decide whether it’s safe to proceed, safe to dismiss, or something they need to read carefully first.
The risk compounds on any product with more than one dialog type in circulation: a settings panel, a destructive confirmation, and a session-timeout warning all sound identical if none of them carry a name. A sighted user tells them apart instantly from the printed heading; a screen reader user gets no equivalent shortcut and has to read into the dialog’s body just to find out which one just took over the screen.
Who is affected?
- Screen reader users: hear the bare role with no further context and have to read into the dialog’s contents to work out its purpose, a step a sighted user skips by glancing at the heading.
- Cognitive disabilities: users who rely on a screen reader to reduce reading load lose the immediate orientation a named dialog provides, right at the moment a dialog is most often asking them to confirm something consequential.
What users experience
Colm uses JAWS on Windows to manage shared folders in a cloud storage app. He opens the overflow menu on a folder and selects “Delete,” which triggers a confirmation dialog built as an unlabelled role="dialog" element. JAWS announces “dialog” and nothing more, so Colm can’t tell from the announcement alone whether it’s confirming the folder he just selected or a different pending action higher up the page. He tabs through every line inside the dialog to find the folder’s name before he’ll confirm, a check a sighted user makes in under a second by reading the modal’s printed heading.
How do I fix it?
Point aria-labelledby at the id of the dialog’s own visible heading whenever one exists. This is the strongest fix because the heading text is already doing double duty for sighted users, so nothing new has to be written or kept in sync: the same string that reads on screen becomes the name a screen reader announces.
When the dialog has no heading (a compact confirmation prompt with just a sentence and two buttons, for instance), add aria-label directly on the dialog element with a short phrase naming what it’s confirming, such as aria-label="Delete folder confirmation". This works because aria-label needs no visible markup to attach to, which suits a dialog whose whole design is deliberately minimal.
Whichever method is used, the naming attribute has to sit on the same element carrying the role, not a wrapper one level up, and not a child element inside the dialog’s body, since that’s the element the accessible name computation actually inspects.
Code Examples
<div role="dialog" aria-modal="true">
<button aria-label="Close">×</button>
<p>Delete "Q3 Reports"? This cannot be undone.</p>
<button>Delete folder</button>
</div><!-- Method 1: aria-labelledby pointing at a visible heading (broadest support) -->
<div role="dialog" aria-modal="true" aria-labelledby="delete-folder-title">
<button aria-label="Close">×</button>
<h2 id="delete-folder-title">Delete "Q3 Reports"?</h2>
<p>This cannot be undone.</p>
<button>Delete folder</button>
</div>
<!-- Method 2: aria-label when there's no visible heading to reuse -->
<div role="dialog" aria-modal="true" aria-label="Delete folder confirmation">
<button aria-label="Close">×</button>
<p>Delete "Q3 Reports"? This cannot be undone.</p>
<button>Delete folder</button>
</div>Method 1 promotes the folder name into a real <h2> that carries the accessible name and reads clearly for sighted users at the same time. Method 2 keeps the original compact layout and names the dialog through the attribute alone, useful when adding a visible heading would change the design more than necessary.
Common Mistakes
Mistake: “The dialog has a visible heading, so it must be fine.” A heading only becomes the dialog’s accessible name once it’s programmatically connected with aria-labelledby. A screen reader reads the DOM’s stated relationships, not visual position: a heading sitting inside the dialog with no id/aria-labelledby pair contributes nothing to its name.
Mistake: “The close button already has aria-label, so the dialog counts as labelled too.” The close button’s aria-label names the close button, not the surrounding dialog. Those are two separate elements, each needing its own accessible name: a perfectly labelled close button next to an unlabelled dialog container still fails this rule.
Mistake: “Putting aria-label on the dialog’s outer wrapper div works the same as putting it on the role itself.” The accessible name computation only looks at the element that actually carries role="dialog" or role="alertdialog". A naming attribute placed on a wrapper element one level outside that role never reaches the element that needs it.
How RedFlag Detects This
Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s dialog-name rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. This page’s redflag-dialog-name frontmatter ID is a docs-slug alias RedFlag’s URL map uses so the extension’s “View docs” link resolves correctly; detection itself is unmodified, stock axe-core, not a RedFlag-authored check. The rule selects any role="dialog", role="alertdialog", or native <dialog> element and checks whether the accessible name computation resolves to non-empty text.
False negative: axe-core confirms a name exists; it can’t tell whether an aria-labelledby target contains an accurate name; a dialog labelled by unrelated text on the page still passes the automated check. False positive: none typical for this check, since the presence of a resolvable name is a binary condition. Manual step: confirm the announced name actually describes the specific dialog’s purpose, not just that some name is present.
Manual Testing
- Open the page in Chrome with NVDA or JAWS running.
- Trigger every dialog on the page: confirmation prompts, settings panels, and alert dialogs alike.
- Listen to what’s announced immediately after the dialog opens: it should be a role (“dialog”) followed by a specific name, not just “dialog” alone.
- Repeat with VoiceOver on an iPhone or Mac for any dialog reachable on mobile, since focus handling and name announcement can differ slightly by platform.
- If the announced name comes from
aria-labelledby, cross-check it against the dialog’s actual content to confirm it matches, rather than pointing at unrelated leftover text.
Related WCAG Success Criteria
4.1.2 Name, Role, Value: Every interface component must expose a name, role, and current value to assistive technology. An unnamed dialog has a role, announced as “dialog,” but no name, which is exactly the gap this criterion requires closing.
2.4.6 Headings and Labels: Headings and labels must describe topic or purpose. Where a dialog’s aria-labelledby points at a heading, that heading needs to genuinely describe the dialog’s purpose, not merely exist: this rule confirms a name is present, while 2.4.6 is about that name actually being descriptive.
Related Issues
Modal opens without moving focus into it covers a different, equally common modal failure: an unnamed dialog often ships alongside a dialog that never receives keyboard focus when it opens, compounding one interaction into two separate problems.
Modal is missing focus management is the automated DOM-detector counterpart, checking whether the modal container itself can receive focus at all, independent of whether it’s named.
Hidden component exposed to screen readers is worth checking on the same dialogs, since a poorly built modal can be simultaneously unnamed and incorrectly exposed to, or hidden from, the accessibility tree.
ARIA command has no accessible name covers the dialog’s own action buttons: a correctly named dialog can still contain buttons that fail this related, more common rule.
References
- W3C Understanding 4.1.2: Name, Role, Value
- ARIA Authoring Practices: Dialog (Modal) Pattern
- MDN: aria-labelledby
Frequently asked questions
Does the title attribute work as an accessible name for a dialog?
Not reliably. The title attribute produces a mouse-hover tooltip in visual browsers, but screen reader support for reading it as a name is inconsistent, and it is never available to touch or keyboard-only users at all. Use aria-labelledby or aria-label instead.
Is an unlabelled dialog a WCAG Level A or AA failure?
Level A. It fails 4.1.2 Name, Role, Value, which is a Level A success criterion, the baseline every WCAG conformance level requires rather than an enhancement reserved for AA or AAA.
Does the native HTML dialog element need this fix too, or only role="dialog"?
Both. The native dialog element and any element with role="dialog" or role="alertdialog" resolve their accessible name the same way, through aria-labelledby, aria-label, or a fallback specific to the element, so an unlabelled native dialog fails identically to an unlabelled ARIA one.
Should a confirmation dialog use role="dialog" or role="alertdialog"?
Use alertdialog when the dialog interrupts the user to demand an immediate response to something urgent, such as confirming a destructive delete action. Use the plain dialog role for anything else, such as a settings panel or a form in a modal. Both roles need an accessible name for the same reason.
Does giving the dialog a visible close button fix the missing name?
No, a close button solves a separate problem. A close button needs its own accessible name (commonly aria-label="Close") so screen readers announce what it does, but that name describes the button, not the dialog itself. The dialog container still needs its own aria-labelledby or aria-label independently.