The Problem: Accessible Forms Need Unique IDs
Associating a <label> with an <input> for accessibility requires a matching id/htmlFor pair. Hardcoding an id works for one instance of a component, but breaks the moment that component is rendered more than once on the same page — both instances would share the same id.
The Problem with a Hardcoded id
function EmailField() {
return (
<>
<label htmlFor="email">Email</label>
<input id="email" type="email" />
</>
);
}
// Rendering <EmailField /> twice on the same page creates two elements
// with id="email" — invalid HTML, and labels no longer point correctly.What useId Does
useId() generates a unique ID string that stays stable across re-renders of the same component instance, safe to use in accessibility attributes — and critically, guaranteed to match between the server-rendered HTML and the client during hydration.
Using useId for a Label/Input Pair
import { useId } from "react";
function EmailField() {
const id = useId();
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} type="email" />
</>
);
}
// Every <EmailField /> instance now gets its own unique id automaticallyWhy Not Math.random() or a Counter?
Generating an id with Math.random() or an incrementing counter produces a different value on the server versus the client during the first render, causing a hydration mismatch — exactly the kind of bug useId is specifically designed to prevent.
Not for List Keys
useId generates one stable id per component instance — it’s not meant for generating keys when rendering a list of items, which should come from the data itself (see the Rendering Lists & Keys lesson).
Best Practice
Use useId anywhere a component needs to generate its own id for accessibility attributes (label/input pairs, aria-describedby), especially in any component or design system meant to be reused more than once per page.