( Robust / WCAG 4.1.2 )
Duplicate id used in an ARIA reference
What is this issue?
An id value used as the target of aria-labelledby or aria-describedby (the two ARIA attributes that build an element’s accessible name and description from another element’s text) appears on more than one element elsewhere in the DOM. This is the narrowest and most consequential case of the general duplicate id failure, specifically for ids that assistive technology directly resolves into announced content.
Unlike a duplicate id on a purely decorative or unreferenced element, this case guarantees a real, observable defect the moment a screen reader encounters the affected element: the browser resolves aria-labelledby="hint" or aria-describedby="hint" to the first element with id="hint" in document order, regardless of which one the author actually intended to supply the name or description.
Why does this matter?
aria-labelledby and aria-describedby exist specifically so that accessible name and description text can be reused from elsewhere on the page instead of duplicated in an aria-label string. That reuse depends entirely on the referenced id pointing at exactly one, correct piece of text; the moment it points at two, the mechanism doesn’t fail loudly, it fails silently and wrongly, announcing whichever duplicate happens to come first.
Because accessible name and description are what a screen reader user relies on instead of visual layout to understand what an element is and what it expects, a corrupted reference here isn’t a cosmetic bug; it’s the equivalent of a sighted user seeing the wrong label next to a form field or dialog, except with no way to tell, since the wrong announcement sounds exactly as confident and complete as a correct one would.
Who is affected?
- Screen reader users: hear a name or description resolved from the wrong element entirely, with no indication anything is amiss, since the announcement sounds like a normal, complete one regardless of whether it’s correct.
- Cognitive disabilities: encountering an announced description that doesn’t match the actual context (a validation hint or format requirement meant for a different field) can lead to genuine confusion about what’s being asked of them, with no obvious explanation available.
What users experience
Wei uses VoiceOver on macOS to fill out a two-field password reset form (a “New password” field and a “Confirm password” field) where both fields’ format-requirement hints were built with aria-describedby="hint", and both hint elements share id="hint" due to a copy-pasted component. She focuses the “Confirm password” field, and VoiceOver announces the first hint’s text, “Must be at least 8 characters,” even though the actual hint intended for the confirm field was “Must match the password entered above.” She types a password that meets the length requirement but doesn’t match her first entry, submits the form, and receives a generic “passwords don’t match” error with no clue that the description she’d relied on was never the one meant for that field.
How do I fix it?
Give every id that’s targeted by an aria-labelledby or aria-describedby reference a unique value across the page, and update every reference pointing at it to match. This works because it restores the one-to-one relationship these attributes assume: one id, one specific piece of text, resolved correctly and consistently every time assistive technology looks it up.
This failure shows up most often in repeated form or card components: two similar fields each referencing an id like "hint" or "error" that was hardcoded into a shared template. Fix it at the template level by deriving each instance’s id from something guaranteed to vary, such as the field’s own name or a loop index, so the fix scales automatically as the component renders more instances rather than needing to be reapplied by hand.
Code Examples
<span id="hint">Optional</span>
<input aria-describedby="hint">
<span id="hint">Required</span><span id="hint-email">Optional</span>
<input aria-describedby="hint-email">
<span id="hint-phone">Required</span>
<input aria-describedby="hint-phone">The original markup has two hint spans sharing id="hint", so the single <input> referencing it resolves to whichever hint comes first in document order, “Optional,” regardless of which field it was actually meant to describe. The fixed version gives each hint its own unique, purpose-named id, and pairs each input with the specific hint that’s actually relevant to it, so aria-describedby resolves correctly for both fields.
Common Mistakes
Mistake: “Only the first occurrence of a duplicated id actually matters, since that’s the one that gets used.” This framing accepts the bug as a feature; the second (or any later) element referencing that id, or being referenced by it, is left permanently broken. The fix is to give every distinct piece of content its own id, not to treat “whichever one wins the collision” as an acceptable outcome.
Mistake: “The hint text happens to be correct for both fields today, so the duplicate id isn’t causing real harm.” Coincidentally matching content today provides no guarantee the text stays in sync after a future edit, and the underlying markup is still invalid HTML regardless of what currently happens to be inside either element. Fix the structural id collision on its own merits, not based on whether today’s content happens to mask it.
Mistake: “aria-labelledby and aria-describedby are read together, so a small mistake in one is not a big deal.” They serve different, specific purposes: aria-labelledby supplies the accessible name, which many screen readers announce first and rely on for identification, while aria-describedby supplies supplementary description text announced afterward. A wrong or missing value in either one is a distinct, independently consequential failure, not a minor variation on the same problem.
How RedFlag Detects This
Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s duplicate-id-aria rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. The rule identifies every id value referenced by an aria-labelledby or aria-describedby attribute anywhere on the page, then flags any of those ids that appear on more than one element in the DOM.
False negative: none typical, since confirming whether a referenced id string is duplicated among the page’s elements is a deterministic comparison axe-core performs reliably for every ARIA reference present at scan time. False positive: none typical for the same reason: a referenced id either matches more than one element or it doesn’t, with no judgment call involved in the detection itself. Manual step: for every flagged pair, use a screen reader to confirm which of the duplicated elements’ text is actually being announced, and verify it’s the content genuinely relevant to the element you’re focused on.
Manual Testing
- Open the page in Chrome or Firefox with NVDA or VoiceOver running.
- Open DevTools and search the page source for
aria-labelledbyandaria-describedbyattribute values. - For each one, check whether the referenced
idappears on more than one element. - If it does, Tab to (or otherwise focus) the element carrying that ARIA reference and listen to what’s actually announced as its name or description.
- Confirm the announced text matches the content genuinely intended for that specific element, not a duplicate element elsewhere on the page.
Related WCAG Success Criteria
4.1.2 Name, Role, Value: Every interface component must expose a correct name, role, and value to assistive technology. This rule maps directly to 4.1.2’s “name” and description requirements: when a duplicated id causes aria-labelledby or aria-describedby to resolve to the wrong text, the affected element’s exposed accessible name or description is factually incorrect, which is precisely what 4.1.2 requires to be accurate.
4.1.1 Parsing: Markup must be well-formed, with id values unique across the document. The root cause of this rule’s failure is a 4.1.1 parsing violation; this rule documents the specific, most consequential downstream effect of that violation on assistive technology.
Related Issues
Duplicate ID attributes found on the page is the broader parent check this rule narrows: any duplicate id anywhere on the page, whether or not it’s referenced by an ARIA attribute.
Element with role uses an ARIA attribute the role prohibits shares this rule’s category of ARIA markup that’s syntactically present but structurally invalid in a way that breaks assistive technology’s interpretation of the page.
Content is hidden from assistive technology at the document body level is unrelated in mechanism but worth auditing alongside this rule, since both are page-level ARIA defects rather than single-element ones.
Focusable element is hidden from assistive technology shares this rule’s broader theme of ARIA state and structure silently diverging from what a sighted user experiences.
ARIA role is invalid or misspelled is a foundational ARIA correctness check worth reviewing on the same page, since a page prone to duplicate-id ARIA reference errors often has other ARIA authoring mistakes as well.
References
Frequently asked questions
Why is this rule rated critical while the other duplicate-id rules are rated lower?
Because the failure directly corrupts an element's accessible name or description (the exact information a screen reader user relies on to understand what something is or does) rather than affecting a secondary behavior like styling or click targeting. A wrong or missing accessible name on a critical control, like a delete or submit button, can lead a user to take an action they did not intend.
What is the difference between aria-labelledby and aria-describedby in how they're affected by this?
Both resolve their referenced id to text using the same first-match mechanism, so both are affected identically by a duplicate id. aria-labelledby supplies an accessible name (what the element is called) while aria-describedby supplies an accessible description (additional detail read after the name). A duplicate id used by either one produces the same class of wrong-content failure, just for a different part of what gets announced.
Can a duplicate id used only by aria-describedby, not aria-labelledby, still be a critical problem?
Yes, though the severity depends on what the description conveys. A wrong description matters most when it carries information the name alone does not, such as a validation hint, a warning, or required-field detail; a user who never hears the correct description may misunderstand what is expected of them, even if the element's basic name is still announced correctly.
Does this rule apply to aria-controls and aria-owns as well?
The same underlying risk exists for any ARIA attribute that references an id: aria-controls, aria-owns, aria-activedescendant, and others all resolve their target the same way. This specific rule is scoped to aria-labelledby and aria-describedby, the two most common naming and description references, but a duplicate id used by any ARIA reference attribute carries the same first-match resolution problem.
If the duplicated element happens to contain the same text both times, is this still worth fixing?
Yes. Text that happens to match today is not a structural guarantee it will keep matching after a future content update, and the duplicate id itself remains invalid HTML regardless of what text currently sits inside either element. Treat matching text as coincidental, not as a reason to leave the underlying id collision unresolved.