useId

useId() returns a string unique to that call in that component instance and stable across re-renders. It exists for attributes that must point at each other — htmlFor/id, aria-describedby — inside a component that may appear many times on one page, where a hardcoded id would collide. One call seeds a family of ids.

One call, one id family per instanceJavaScript
function PasswordField() {
  const id = useId();
  return <p>
    <label htmlFor={id + '-in'}>Password </label>
    <input id={id + '-in'} type="password" aria-describedby={id + '-hint'} />
    <span id={id + '-hint'}>At least 12 characters.</span>
  </p>;
}

Render that component twice and React 7,897 generates _r_0_ and _r_1_, so the two aria-describedby pairs never collide. The shape is deliberate: React 19.2 replaced the older :r0: prefix because a colon is not valid in a CSS selector or an XML name, and these ids work with document.querySelector('#' + id) unescaped.

The value comes from the component's position in the tree, not a counter. That is what makes server rendering work: server and client produce identical ids while they render the same tree, and diverging trees produce a hydration mismatch. Two things useId is not for: list keys, which come from your data (Lists and Keys), and cache keys for use.