useId Hook — Creating Unique IDs for SSR and Accessibility
Starting from React 18, a new hook was introduced — useId, which creates unique, stable, and predictable IDs. It's designed for cases where you need an ID that matches during server-side and client-side rendering.
This hook is especially useful in SSR projects (e.g., Next.js), where React first creates HTML on the server, then "rehydrates" it in the browser. If IDs differ on both sides, React will break the DOM connection.
🔹 What Problem useId Solves
Let's say we need to connect an input and label for accessibility purposes. For this, we need a unique ID for each input. However, in SSR projects, if the ID is generated with a random number (e.g., Math.random()), it will be different on the server and client, causing a warning:
Warning: Prop `id` did not match. Server: "input-17" Client: "input-83"
useId creates the same ID on both sides, ensuring stability and preventing hydration issues.
📍 Example 1 — Label and Input Connection
import React, { useId } from 'react';
export default function NameField() {
const id = useId();
return (
<div>
<label htmlFor={id}>Name</label>
<input id={id} type="text" placeholder="Write your name" />
</div>
);
}
Here React will automatically create an ID, for example: react-1:0,
and the same ID will be used in both server-side render and client-side hydration.
📍 Example 2 — Unique IDs for Multiple Inputs
import React, { useId } from 'react';
export default function FormExample() {
const nameId = useId();
const emailId = useId();
return (
<form>
<div>
<label htmlFor={nameId}>Name</label>
<input id={nameId} type="text" />
</div>
<div>
<label htmlFor={emailId}>Email</label>
<input id={emailId} type="email" />
</div>
</form>
);
}
Each `useId()` call creates a stable, unique ID that React generates through its internal calculations. It ensures that when re-render or hydration occurs, the ID remains the same.
📍 Example 3 — Same ID in Different Components (Base ID + Postfix)
If you want to use one `useId()` in multiple places but with different postfixes, you can do it like this:
import React, { useId } from 'react';
export default function RangeSlider() {
const baseId = useId();
return (
<div>
<label htmlFor={`${baseId}-min`}>Minimum value</label>
<input id={`${baseId}-min`} type="range" />
<label htmlFor={`${baseId}-max`}>Maximum value</label>
<input id={`${baseId}-max`} type="range" />
</div>
);
}
This approach allows creating multiple sub-IDs from one base ID, which will also be stable in SSR.
⚙️ What useId Returns
- Returns a string, for example:
react-1:2 - React guarantees its uniqueness within the same render tree.
- Starting from React 18, SSR uses the same seed for ID consistency.
📍 Example 4 — Stability in SSR (Next.js)
When using frameworks like Next.js that server-side render React, ID stability becomes crucial. useId ensures that the same ID is created on both server and client.
// pages/index.js
import { useId } from 'react';
export default function Home() {
const id = useId();
return (
<div>
<label htmlFor={id}>Username</label>
<input id={id} type="text" />
</div>
);
}
Without useId, the server and client would create different random IDs, causing React hydration error. With useId, they match 100%.
📘 Best Practices
- Don't use useId value as a key — React designed it only for IDs.
- Use it when you need to connect label/input, aria-labelledby, or aria-describedby.
- Don't try to manually modify the ID — React manages it.
🎯 Exercise (Try it yourself)
Write a component that:
- Has two input fields (name and email).
- Create a unique ID for each using `useId`.
- Connect the label to the input via `htmlFor`.
- Try it in an SSR (Next.js) environment — React shouldn't complain about hydration.
📘 Summary
- useId — new hook from React 18 that creates unique and stable IDs.
- Useful for SSR projects and accessibility purposes.
- React guarantees that server-side and client-side IDs are the same.
- Suitable for label-input connections and aria properties.