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
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.
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.