HP3003 (duplicate-id) means several elements on the hydrated page share the
same id. Labels, ARIA references and getElementById then find only the
first one. It is an info finding, not a hydration mismatch: give every element
a unique id, and generate the ids of repeated components with useId.
| Code | HP3003 |
|---|---|
| Name | duplicate-id |
| Default severity | Info |
| Group | Markup the browser repaired |
| What it means | Several elements share the same id. |
What the HP3003 duplicate id finding means
After the page settles, hydration-proof checks the whole document for ids that appear more than once:
2 elements (<input>) have id="email". Labels, ARIA references and getElementById find only the first one.A few details of the check:
- Ids inside
<template>elements and shadow roots are not counted, and neither are elements you ignore with[data-hydration-proof-ignore]orignore.selectors. - At most 20 duplicate ids are reported per page.
- When the repeated id was generated by React's
useIdin two separate React roots, the finding is HP3004 instead, with a different fix.
Info findings do not fail the run and are not printed in the terminal list. They appear in the HTML and JSON reports.
Likely causes
- A component with a hard-coded id used twice on a page, such as a newsletter form in both the header and the footer.
- Responsive layouts that render the desktop and the mobile version of the same navigation and hide one with CSS.
- SVG icons with fixed ids in
<defs>(<linearGradient id="gradient">), rendered once per icon. - Content from a CMS that repeats anchor ids.
How to fix it
Give every element a unique id; generate ids for repeated components with
useId. It returns the same value on the server and during hydration, so it
does not create a mismatch.
// Before: every instance renders id="email"
export function NewsletterForm() {
return (
<form>
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" />
</form>
);
}import { useId } from "react";
// After: each instance gets its own id
export function NewsletterForm() {
const id = useId();
return (
<form>
<label htmlFor={`${id}-email`}>Email</label>
<input id={`${id}-email`} type="email" name="email" />
</form>
);
}The same works for SVG definitions:
import { useId } from "react";
export function Logo() {
const gradientId = useId();
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<defs>
<linearGradient id={gradientId}>
<stop offset="0" stopColor="#0ea5e9" />
<stop offset="1" stopColor="#6366f1" />
</linearGradient>
</defs>
<circle cx="12" cy="12" r="10" fill={`url(#${gradientId})`} />
</svg>
);
}Do not replace a hard-coded id with a counter or a random value: the server and the browser would generate different ids, which turns this info finding into a real hydration mismatch.
Catch it with ESLint
no-unstable-id reports ids built from
Math.random(), Date.now() or module-level counters, the usual wrong fix for
a duplicate id. A hard-coded id repeated by two instances of a component is
only visible on the rendered page.
When the duplicate is intentional
Add an ignore.issues rule with code: "HP3003", the element's selector and
a reason (see ignoring findings). checks.invalidHtml: false
turns off every HP3xxx check, including this one.