( Robust / WCAG 4.1.1 )

Duplicate ID attributes found on the page

ModerateLevel AWCAG 4.1.1 — Parsing

What is this issue?

Two or more elements in the page’s DOM carry an identical value for the id attribute. HTML requires id values to be unique across the entire document; this isn’t a style preference, it’s a parsing rule, which is why this check maps to the WCAG 4.1.1 Parsing criterion rather than a naming or labeling criterion.

Browsers don’t reject duplicate ids outright; they render the page anyway and quietly resolve any lookup by that id to the first matching element in document order. That silent, fault-tolerant recovery is exactly what makes this defect easy to miss during normal visual testing: the page looks fine, right up until something that depends on the id behaves unexpectedly.

Why does this matter?

An id is meant to be a single, unambiguous address for one specific element. The moment two elements share one, every mechanism that resolves an id to an element (label[for], aria-labelledby, aria-describedby, in-page anchor links, document.getElementById()) has to guess, and browsers consistently resolve to the first matching element regardless of which one was actually intended.

This produces failures that are hard to diagnose because nothing throws a visible error. A form field’s label might silently point at the wrong copy of a repeated component. A screen reader might announce a description meant for a different card, dialog, or list item. The bug looks like a labeling mistake or an ARIA mistake when the real root cause is an id collision several layers upstream of the symptom someone actually notices.

Who is affected?

  • Screen reader users: hear a name or description resolved from whichever duplicate element the browser matched first, which may describe an entirely different piece of content than the one they’re actually interacting with.
  • Keyboard users: following an in-page anchor link (href="#section") land at the first element with that id, not necessarily the one the link’s text described, if the link was meant to target a later duplicate.
  • Cognitive disabilities: encountering a mismatched label or description caused by an id collision experience it as confusing or contradictory content, with no way to know the actual cause is a markup defect rather than something they misunderstood.

What users experience

Jae uses NVDA to compare three pricing tiers on a subscription page built from a repeated card component that, due to a bug, renders the same id="tier-description" on every card instead of generating a unique one per tier. He tabs to the “Upgrade” button on the Professional tier and NVDA announces the description text from the Basic tier instead, since the browser resolves aria-describedby="tier-description" to the first matching element on the page regardless of which card the button actually belongs to. He upgrades based on incorrect information about what he’s purchasing, because the accessible description he heard didn’t match the plan he was actually selecting.

How do I fix it?

Make every id on the page unique, using a generation strategy that guarantees uniqueness even when the same component renders multiple times. This works because it restores the one-to-one mapping every id-dependent mechanism assumes exists: once no two elements share an id, every label, ARIA reference, and anchor link resolves unambiguously to the element that was actually intended.

The most common source of this failure is a component (a card, a form field, a modal) that’s copy-pasted or rendered in a loop with a hardcoded id baked into its template. Fix it at the template level by deriving each instance’s id from something guaranteed to vary per instance, such as a loop index or a unique data key, rather than patching individual rendered copies by hand.

Code Examples

Before
<div class="card">
  <h2 id="card-title">Product A</h2>
</div>
<div class="card">
  <h2 id="card-title">Product B</h2>
</div>
After
<!-- Method 1: derive a unique id per instance -->
<div class="card">
  <h2 id="card-title-1">Product A</h2>
</div>
<div class="card">
  <h2 id="card-title-2">Product B</h2>
</div>
// Method 2: generate the id dynamically from a stable per-item key
const id = `card-title-${product.id}`;

Method 1 shows the end result: appending an index or key to the base id guarantees every rendered card gets its own unique value instead of colliding on the shared literal string "card-title". Method 2 shows where that fix actually belongs: in the component or template that generates the markup, so the uniqueness holds automatically no matter how many cards render, rather than needing to be re-applied by hand every time the list changes.

Framework Examples

React’s useId() hook generates a unique, stable identifier per component instance specifically to solve this class of bug, since a hardcoded string id inside a reusable component collides the instant that component renders more than once on the same page.

import { useId } from 'react';

function PricingCard({ title, description }) {
  const titleId = useId();

  return (
    <div className="card">
      <h2 id={titleId}>{title}</h2>
      <p aria-describedby={titleId}>{description}</p>
    </div>
  );
}

useId() is worth knowing specifically because the bug it prevents is invisible during single-instance development and testing: a component with one hardcoded id renders correctly and passes every check the first time it’s used, and only breaks the moment a second instance of the same component appears on the same page, which is exactly the scenario useId() is built to handle safely.

Common Mistakes

Mistake: “The duplicate elements are visually far apart, so their ids don’t actually conflict.” Visual distance on the page has no bearing on id uniqueness: the DOM is a single flat namespace for id values regardless of how far apart the elements sit visually or in the layout. Two elements at opposite ends of a long page with the same id collide exactly as much as two elements sitting side by side.

Mistake: “The browser didn’t show an error, so the duplicate ids must not be causing a real problem.” Browsers deliberately recover from invalid HTML rather than halting rendering, which means a duplicate id produces no visible error, no console warning, and a page that looks completely normal, right up until something depending on that id, like a label or an anchor link, resolves to the wrong element and the symptom appears somewhere else entirely.

Mistake: “Only ids referenced by ARIA attributes or labels actually matter; decorative ids are harmless to duplicate.” Any id can become load-bearing later, whether through a script added afterward, an anchor link introduced in different content, or a future ARIA attribute referencing it. Treating some ids as “safe to duplicate because nothing uses them yet” is a bet against future changes to the page, not a guarantee.

How RedFlag Detects This

Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s duplicate-id rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. The rule collects every element’s id attribute value across the page and flags any value that appears on more than one element.

False negative: none typical for this specific check, since detecting whether two elements share an identical string value is a deterministic comparison axe-core performs reliably: every duplicate that exists in the static DOM at scan time is caught. False positive: dynamically-generated ids that are duplicated only briefly during a state transition, and resolved before the user notices, could theoretically be flagged if the scan captures that exact moment, though this is uncommon in practice. Manual step: for every flagged duplicate, check what, if anything, currently references that id (a label, an ARIA attribute, an anchor link, or a script) to understand the real-world severity, since a duplicate on two entirely unreferenced elements is a lower-priority fix than one on an element something actually depends on.

Manual Testing

  1. Open the page’s HTML source or the browser’s Elements panel in DevTools.
  2. Search for repeated id="..." values across the document.
  3. For each duplicate found, identify anything that references that id: a <label for>, an aria-labelledby/aria-describedby, an anchor link, or a script call to getElementById.
  4. With NVDA or VoiceOver running, navigate to each of the duplicate elements and confirm the announced name or description matches the element you’re actually on, not a different duplicate elsewhere on the page.
  5. Flag any case where the announced content doesn’t match the element the user is currently focused on.

4.1.1 Parsing: Markup must be well-formed, with elements correctly nested and no duplicate attributes where uniqueness is required, including id. Duplicate ids are a textbook 4.1.1 failure, since id uniqueness is a parsing-level requirement of HTML itself, independent of any specific labeling or ARIA rule.

4.1.2 Name, Role, Value: Every interface component must expose a correct name, role, and value to assistive technology. When a duplicate id causes a label or description to resolve to the wrong element, the affected component’s exposed name becomes incorrect even though the underlying markup for that component may otherwise be well-formed, making duplicate ids a common secondary cause of 4.1.2 failures elsewhere on a page.

Duplicate id on active, focusable elements narrows this exact failure to the more severe case of a duplicate id appearing on interactive elements, where focus and keyboard behavior are directly affected.

Duplicate id used in an ARIA reference narrows this failure to the case where the duplicated id is specifically the target of an ARIA reference attribute, where accessible name and description resolution are directly affected.

Element with role uses an ARIA attribute the role prohibits shares this rule’s theme of markup that looks superficially valid but fails a structural rule assistive technology depends on.

Content is hidden from assistive technology at the document body level is unrelated in mechanism but commonly appears in the same audit pass as duplicate-id issues, since both are structural, whole-page defects rather than single-element ones.

Focusable element is hidden from assistive technology shares this rule’s broader category of markup defects that silently break assistive technology’s model of the page without any visible symptom.

References

Frequently asked questions

Is a duplicate id ever valid HTML?

No. The HTML specification requires every id value on a page to be unique among all elements in the document, with no exceptions. A duplicate id is invalid HTML even before considering its accessibility consequences: browsers tolerate it and keep rendering, but it is a parsing error, not a permitted pattern.

Why do duplicate ids not show an error in the browser the way a broken link does?

Because HTML parsing is designed to be fault-tolerant: browsers recover from invalid markup rather than stopping the page from rendering. A duplicate id produces no visible error and no console warning in most browsers, which is exactly why it tends to go unnoticed until something that depends on id uniqueness, like a label or an anchor link, quietly breaks.

Do CSS selectors like #my-id also break when an id is duplicated?

Yes. A CSS rule targeting #my-id applies its styles to every element with that id, not just the one the author intended, which can produce unexpected styling on the duplicate element as a side effect. It is a smaller, usually more visible symptom of the same underlying uniqueness violation this rule targets.

What is the difference between this rule and duplicate-id-active or duplicate-id-aria?

This is the general check, covering any duplicate id anywhere on the page regardless of what references it. duplicate-id-active narrows the scope to duplicate ids specifically on focusable, interactive elements, where keyboard and focus behavior is affected. duplicate-id-aria narrows it to duplicate ids that are the target of an ARIA reference attribute, where accessible name and description resolution is affected.

Can two hidden elements share the same id if neither is ever shown?

No, this still violates the rule and still carries risk. An element hidden today with CSS or an attribute can become visible later through a state change, a responsive breakpoint, or a script update, and duplicate ids that were harmless while hidden become an active problem the moment either element becomes reachable or referenced. Unique ids are cheap to maintain from the start; treat "currently hidden" as no exception.