HTML Id
html id the id attribute provides document-unique hooks for fragments, form labels, and the id attribute must be unique
Introduction
The id attribute must be unique per document — fragment URLs (#section), label associations (for/id), ARIA references (aria-labelledby), and CSS/JS hooks depend on it. Duplicate ids break accessibility APIs and querySelector unpredictability. Google One Tap and Stripe Elements document required container ids; BBC skip links target #main.
Business problem
Duplicate ids from copy-paste components, CMS loops, and React key mistakes cause wrong label association, broken in-page links, and GET form submissions targeting wrong field.
- A11y: label for=email points to first id=email — second field unlabeled.
- Support: Hash link #pricing scrolls to wrong section on long marketing pages.
- Legal: WCAG 4.1.1 Parsing — duplicate ids fail compliance scans.
Why this feature exists
Fragment identifiers in URLs required named anchors — id replaced name attribute. Uniqueness enables single-point references across linking, CSS #foo, and getElementById O(1) map.
- History: name anchors on a deprecated — id on any element.
- XML: Stricter id rules — HTML5 allows almost any string except spaces.
- Shadow DOM: ids scoped inside shadow — document-wide unique in light DOM only.
Browser internals
Document.getElementById returns first match in tree order — duplicates leave second orphaned for API purposes. CSS #id specificity (1,0,0) beats classes. url fragment navigates to element with matching id — scroll-margin-top affects landing.
- aria-labelledby: Space-separated ids reference multiple elements.
- Invalid: id starting with digit — CSS escape needed, valid in HTML5.
- Dynamic: SPA route change shouldn't duplicate static layout ids across virtual pages if DOM persists.
getElementById('x') → first #x in document#pricing { scroll-margin-top: 80px; }aria-describedby="err1 err2" → concat accessible description
Rendering workflow
Fragment navigation triggers scroll without full navigation — instant for INP. Sticky header obscuring id target — fix with scroll-margin on :target. LCP unaffected unless hash scroll delays hero paint perception.
- Scroll anchoring: Dynamic content above target shifts scroll — anchor nodes help.
- Focus: Focus management on route id section — tabindex=-1 on h1 pattern.
- History: pushState with hash updates scroll and screen reader context.
Feature deep dive
Use id for: fragment destinations, label/input pairing, ARIA relationships, and unique widget roots. Avoid styling with id — use class. Generate ids for repeated components with UUID suffix in SPAs.
- Skip link: href="#main" + main id="main".
- Heading ids: Auto from slug for deep links in docs — Stripe API reference pattern.
- Forms: id on input matches label for — required for click-to-focus.
<a href="#faq">FAQ</a>…<section id="faq" aria-labelledby="faq-heading"><h2 id="faq-heading">FAQ</h2></section><label for="email">Email</label><input id="email" name="email" type="email">
Accessibility analysis
IDREF relationships power accessible name/description computation — broken id breaks entire form. Duplicate ids cause AT to reference wrong node — silent catastrophic failure. WCAG 4.1.1 requires unique ids where used.
- aria-controls: Points to id of controlled region — tab panels.
- aria-labelledby: Multiple ids — order matters in name.
- Focus: Move focus to id=main on skip — tabindex=-1 on main.
SEO impact
Fragment URLs (page#section) may index as separate URLs if content substantially different — usually consolidated. Google sitelinks jump to hash sections on brand queries. Descriptive id slugs minor UX win in shared links — not ranking factor.
- Canonical: Hash not sent to server — same URL base, different client section.
- Jump links: Table of contents with hash links helps long article UX — indirect engagement signal.
- Crawl: Googlebot follows hash in some JS apps — prefer real URLs for routes.
Security considerations
Fragment XSS historically — location.hash to innerHTML. Sanitize hash usage. Predictable ids (user-123) enable DOM clobbering if combined with named properties. Don't put secrets in id values — visible in DOM and URLs.
- Open redirect: Unrelated to id — but hash navigation to attacker content in SPA if router naive.
- Clobbering: id=length on input clobbers HTMLCollection — rare, sanitize user widgets.
- Enumeration: Sequential ids in HTML leak object counts — low risk.
Performance impact
getElementById fast — prefer over querySelector('#x') in hot paths marginally. Thousands of unique ids don't hurt; duplicate ids cause wrong node updates — logical bugs. Auto-generated long UUID ids bloat HTML slightly on 500-field admin pages.
- CSS: #id rules prevent tree-shaking unused rules — avoid id selectors in DS.
- Hydration: Stable ids between server/client required for ARIA wiring.
- SPA: Reuse ids after route swap without cleanup — duplicate explosion.
Real production example
Google documentation auto-ids headings from text slug — stable permalinks in MDX pipeline. Airbnb listing share links use canonical URL without fragile hash. Form fields use ids from server with collision check in component library.
- Lint: eslint-plugin-unique-id or axe duplicate-id rule in CI.
- CMS: Block authors from setting raw id — auto-generate.
- Microfrontend: Prefix ids with app name — mfe-checkout-email.
<!-- Stripe-style mount point --><div id="payment-element"></div><label for="mfe-billing-email">Email</label><input id="mfe-billing-email" …>
Enterprise usage
Enterprise a11y gates fail builds on duplicate id in static analysis of rendered HTML samples. SAP Fiori documents id prefix per app namespace. Legal discovery exports preserve fragment links to deposition exhibits.
- Testing: Playwright getByRole preferred — getByTestId data-testid avoids id coupling.
- i18n: id stable across locales — label text changes, id doesn't.
- Email: Avoid id for styling — limited client support for fragments.
Common production failures
Component library shipped id="content" on every card — axe duplicate-id critical on PLP. Hash #terms scrolled to footer duplicate id on mobile-only terms block. React strict mode double mount exposed duplicate ids in dev mistaken for prod bug.
- Label: for attribute pointed to wrong duplicate — PCI field mislabeled.
- CSS: #header global style broke embedded widget also using id=header.
- Analytics: getElementById tracked wrong button — revenue misattributed.
Architecture review questions
- Is every id unique in the document after hydration and dynamic inserts?
- Are label for, aria-labelledby, and aria-describedby idrefs valid?
- Do skip links and in-page nav target the correct id destinations?
- Are ids stable across SSR and client renders for ARIA wiring?
- Is id used for semantics/relationships, not primary styling hooks?
- Do micro-frontends prefix ids to avoid collision in shared layouts?
Hands-on project
Add duplicate-id lint to CI on rendered HTML for top templates; fix collisions; implement skip-to-main and heading permalink ids with scroll-margin.
- Deliverable: axe duplicate-id zero on key flows.
- Verify: Two email fields on same page — distinct ids and labels.
- Stretch: Namespace prefix convention doc for MFEs.
Interview questions
What breaks when duplicate ids exist in one document?(Advanced)
getElementById and label for bind first match only; CSS #id applies to all but invalid HTML; ARIA IDREFs may reference wrong node; fragment navigation ambiguous. axe flags 4.1.1 violation. Fix with unique generation in lists.
Follow-up: Are duplicate ids in shadow DOM ok?
id vs class for CSS hooks — staff guidance?(Advanced)
Avoid styling ids — specificity blocks overrides and prevents reuse. Reserve id for uniqueness contracts: fragments, ARIA, labels. Classes for visual patterns. Exception: third-party mount ids required by vendor.
Follow-up: When is CSS #header acceptable?
SPA route changes and id lifecycle?(Advanced)
Unmount old view ids before mounting new or generate per-route prefixed ids. Persistent layout ids (main, nav) stable. Dialog ids per open instance with UUID. Test duplicate after client navigation in axe.
Follow-up: Hash routing vs pathname for SEO?
Try it yourself
Edit the HTML, CSS, or JS panels — the preview updates as you type.
Try it yourself
Summary
The id attribute provides document-unique hooks for fragments, form labels, and ARIA wiring. Staff practices at Google and Stripe enforce uniqueness in CI, prefix micro-frontend ids, and never rely on duplicated or presentational id selectors.