Stable, Unique IDs for Accessible Markup
useId
`useId` returns a unique string per component instance — perfect for pairing labels with inputs without hardcoding `id`s.
What you'll learn
- Use `useId` to link `<label>` and `<input>`
- Avoid id collisions in reusable components
For accessible forms, <label htmlFor> needs to match an <input id>.
Hardcoding an id breaks when you mount the same component twice on
the same page — two inputs end up with the same id.
useId generates a unique string per component instance.
The Shape
function NameField() {
const id = useId();
return (
<>
<label htmlFor={id}>Name</label>
<input id={id} type="text" />
</>
);
} Mount NameField twice — each gets its own unique id, labels pair
correctly with their inputs.
Multiple Inputs Per Component
Use the returned id as a prefix:
function NamePair() {
const id = useId();
return (
<>
<label htmlFor={`${id}-first`}>First</label>
<input id={`${id}-first`} />
<label htmlFor={`${id}-last`}>Last</label>
<input id={`${id}-last`} />
</>
);
} NOT for List Keys
useId generates ids for DOM attributes. For React keys in a
.map, you need an id from your data — not useId.
// ✗ useId returns the SAME value across renders for a given instance
// (and it doesn't distinguish list items)
{items.map(item => <Row key={useId()} item={item} />)}
// ✓ Use the item's id
{items.map(item => <Row key={item.id} item={item} />)} Plus, calling a hook inside .map violates the rules of hooks.
SSR
useId produces ids that match on server and client — important
for server-side rendering. Don’t hand-roll Math.random() ids; you’ll
get hydration mismatches.
Up Next
Hooks for telling React “this update isn’t urgent, take your time”.
useDeferredValue →