Navigate
Search topics, hooks, and fundamentals
What if every component instance could get its own unique valet ticket β automatically?
π ΏοΈ Theme: Valet Parking
Your component is a parking lot. Without tickets, two valets serving the same car model hand back the wrong keys β IDs collide. useId is the ticket printer: it stamps a guaranteed-unique ticket for every car, works across multiple lots (server + client rendering), and never duplicates even when the same form appears twice on the page.
Without useId, every component instance uses the same hardcoded ID. Two red Toyotas, same description β the valet always grabs the wrong one.
Click the "Car Owner (Lot B)" label β it focuses Lot A's input instead!
Both labels point to id="car-owner" β the valet always grabs the first car in the lot!
useId prints a unique stub for every component instance. Each car gets its own ticket β labels always retrieve the correct vehicle, and it works the same on the server and client.
Click any label β it focuses the correct input every time!
function OwnerField() {
const id = useId();
// β e.g. ":r1:", ":r2:", ":r3:"
return (
<>
<label htmlFor={id + "-car-owner"}>Car Owner</label>
<input id={id + "-car-owner"} />
</>
);
}
// Two lots β each gets a unique ticket!
<OwnerField /> {/* id=":r1:-car-owner" */}
<OwnerField /> {/* id=":r2:-car-owner" */}
// Labels ALWAYS retrieve the right caruseId is the valet's ticket printer β every component instance gets a unique stub, no matter how many cars are in the lot.
Two playgrounds to explore useId in action β add cars to the lot and watch unique tickets generate, then see how aria-describedby links labels and errors in an accessible form.
Add and remove cars from the lot. Each car automatically gets a unique ticket β click any label to verify it focuses the correct input.
function CarSpot() {
const id = useId();
// Each spot: ":r1:-owner", ":r2:-owner", etc.
return (
<>
<label htmlFor={id + "-owner"}>Owner</label>
<input id={id + "-owner"} />
</>
);
}
// Add as many cars as you want β zero collisions!Click any label to focus its input. Error messages are linked via aria-describedby for screen readers.
const id = useId();
<label htmlFor={id + "-plate"}>License Plate</label>
<input
id={id + "-plate"}
aria-describedby={id + "-plate-error"}
/>
{error && (
<p id={id + "-plate-error"} role="alert">
{error}
</p>
)}
// Screen readers announce the error
// because aria-describedby links themThese are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function UserList({ users }) {
return (
<ul>
{users.map(user => {
const id = useId(); // Hook called in a loop!
return <li key={id}>{user.name}</li>;
})}
</ul>
);
}function EmailField() {
// Different on server vs client β hydration mismatch
const id = `email-${Math.random().toString(36).slice(2)}`;
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} type="email" />
</>
);
}function CreateItem() {
const id = useId();
const handleSubmit = () => {
// Bad: React's internal ID as a database key
api.createItem({ id, name: "New Item" });
};
return <button onClick={handleSubmit}>Create</button>;
}