useId Hook
Overview
When building accessible web applications, you often need to link HTML elements together using IDs. For example, connecting a <label> to an <input> using htmlFor and id, or connecting a modal to its description using aria-describedby.
In a standard HTML page, you just hardcode an ID like id="email-input". But in React, components are highly reusable. If you render your <CustomInput> component 3 times on the same page, and all 3 hardcode id="email-input", you will have duplicate IDs in the DOM, which destroys accessibility for screen readers and breaks label clicking.
React 18 introduced the `useId` hook to solve this. Calling useId() generates a mathematically unique, stable string (like :r3:) that is guaranteed to be unique across your entire application. By using this hook, you can safely render a component 100 times without ever causing an ID collision.
Syntax
import { useId } from 'react';
function PasswordField() {
// Generate a unique ID for this specific instance of the component
const passwordHintId = useId();
return (
<div>
<label>
Password:
{/* We use the unique ID to link the input to its descriptive hint */}
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
Must be at least 8 characters long.
</p>
</div>
);
}Common Pitfalls
Using useId for mapping keys: NEVER use
useId()to generatekeyprops for items in a list (e.g.,<li key={useId()}>). Keys must be tied to the specific data from your database. If you useuseId()in a loop, it generates a new ID based on the render order, completely breaking React's reconciliation engine.
Interview Questions
Why use useId() instead of Math.random() or a library like uuid to generate IDs?
If you use Math.random(), the ID will change every single time the component re-renders, breaking accessibility tools mid-session. Furthermore, if you are using Server-Side Rendering (Next.js), the server will generate a random ID (e.g., 0.5), and the client will generate a different random ID (e.g., 0.9), causing a Hydration Mismatch error. useId() guarantees the exact same stable ID on both the server and the client.
Can you append prefixes or suffixes to the output of useId()?
Yes, this is highly encouraged. A single component might need multiple IDs (e.g., one for the input, one for the error message). You can generate one id = useId() and use template literals to create derivatives: \${id}-input and \${id}-error.
Real-World Example
Reusable Accessible Form Group: By utilizing useId(), we can render this <FormGroup> 50 times on a massive checkout page. Every single label will perfectly link to its respective input, and screen readers will perfectly announce the correct error messages, without any global ID collisions.
import { useId } from 'react';
function FormGroup({ label, errorText, ...inputProps }) {
// Generate one base ID for this form group instance
const baseId = useId();
const inputId = `${baseId}-input`;
const errorId = `${baseId}-error`;
return (
<div className="form-group">
<label htmlFor={inputId}>{label}</label>
<input
id={inputId}
aria-invalid={!!errorText}
aria-errormessage={errorText ? errorId : undefined}
{...inputProps}
/>
{errorText && (
<span id={errorId} className="error-msg text-red-500">
{errorText}
</span>
)}
</div>
);
}Check Your Knowledge
Test your understanding of useId Hook with these quick questions.