useId returns a unique id for each instance of a component, and the same id on every render. Use it to connect a <label> to its <input>, or an input to its hint through aria-describedby, when the component can appear more than once on a page.
The same component renders twice and gets two different ids, printed under each field. Click the text "Confirm password" and focus jumps to the second input, because its htmlFor matches only that input's id. Hardcode id="password" in place of useId() and both labels point at the first input.
The syntax
const id = useId();
useId takes no arguments and returns a string. Call it at the top level of the component, like every hook. Its exact format is internal and has changed between versions: React 18 produced :r1:, and React 19.2 produces _r_1_ for a component first rendered in the browser and an id starting with _R_, built from the tree position, for one rendered on the server. Never parse it or rely on its shape.
Why not Math.random or a counter
Ids for accessibility have to match between the HTML the server sends and the tree React builds in the browser. The two obvious ways to make an id both fail that test.
// Changes on every render, and differs between server and browser
const id = 'field-' + Math.random().toString(36).slice(2);
// The server's counter keeps growing across requests, the browser starts at 0
let nextId = 0;
const id = 'field-' + nextId++;
With server rendering (Next.js, React Router framework mode, any hydrateRoot setup), the server prints id="field-4817" into the HTML, the browser's first render computes field-0, and React reports a hydration mismatch. useId builds the id from the component's position in the tree, which is identical on both sides.
Even without a server, an id built during render can change on every render. This example shows the difference without any server involved:
Click the button a few times. The useId value stays put while the counter id climbs with every render, so anything that pointed at the old id (an aria-describedby, a label) now points at nothing. Wrapping the counter in useState(() => nextId++) would fix the re-renders but not the server mismatch.
Several ids from one call
A component with several fields does not need several useId calls. Generate one base and add a suffix per element.
Type a word without an @ in the email field: the error appears and the input's aria-describedby points at it, so a screen reader reads the error when the input is focused. Render <SignupForm /> twice in App and each copy gets its own base id.
Not for list keys
Keys and ids solve different problems. A key tells React which item is which across renders, so it must come from the data. useId gives one id per component instance, and you cannot call it inside map.
// Wrong: breaks the rules of hooks, and the key is unrelated to the item
{todos.map((todo) => <Todo key={useId()} todo={todo} />)}
// Right: the key comes from the data
{todos.map((todo) => <Todo key={todo.id} todo={todo} />)}
When your data has no id, create one when the item is created (crypto.randomUUID() in the event handler that adds it), not during render. The lists and keys page explains why the key must stay with its item.
Several React roots on one page
If two separate React apps render on the same page, their ids could collide. Give each root a prefix:
createRoot(document.getElementById('cart'), { identifierPrefix: 'cart-' });
createRoot(document.getElementById('chat'), { identifierPrefix: 'chat-' });
With server rendering, pass the same identifierPrefix to the server renderer and to hydrateRoot so both sides produce the same ids.
Common mistakes
Rendering a different tree on the server and in the browser. useId depends on the component's position, so a branch like typeof window === 'undefined' ? <A /> : <B /> above a field can shift the ids between the two renders. Keep the tree the same during hydration and switch after it, in an effect.
Looking the element up by its id. document.getElementById(id) works, but a ref is the React way to reach a DOM node and needs no id at all.
Using it as a random value. The id is unique within the app, not random, and it is predictable from the tree. Do not use it for security tokens, cache keys or anything stored across sessions.
When to use it
Use useId whenever a reusable component needs an id attribute: form fields built from a component used many times, a tooltip linked with aria-describedby, a dialog with aria-labelledby, tabs with aria-controls. When an id only connects a label to its input, you can also skip the id and nest the input inside the label (<label>Name <input /></label>); use useId when the elements cannot be nested.
Frequently Asked Questions
What does useId do in React?
It returns a string that is unique to that component instance and stays the same on every render. You use it to connect elements by id: htmlFor on a label, aria-describedby on an input, aria-labelledby on a dialog.
Why not use Math.random() or a counter for ids?
They give different values on the server and in the browser, so a server rendered page and its hydrated version disagree and React reports a hydration mismatch. Math.random() also changes on every render. useId derives the id from the component's position in the tree, which is the same in both places.
Can I use useId for keys in a list?
No. A key must come from your data so React can match the same item across renders. useId is called once per component, and calling it inside map breaks the rules of hooks anyway. Use the item's own id.
How do I get several ids from one useId call?
Call useId once and add suffixes: ${id}-name, ${id}-email. The base is unique, so the suffixed strings are too.
Can I use the id from useId in a CSS selector?
Avoid it. The id is meant for connecting elements in the DOM, and its exact format is an internal detail that has changed between React versions. Style with a class, and look elements up with a ref instead of querySelector.