useId

useId() takes no parameters and returns a single ID string that's unique to that particular call in that particular component instance, and stays the same across re-renders. It exists for wiring up accessibility attributes that need to reference each other – connecting an <input> to its hint text through aria-describedby, say, or a <label> to its field through htmlFor/id – especially inside a reusable component that might get rendered many times on the same page, where a hardcoded id string would collide.

useId() should not be used to generate keys for a list – keys should always come from your data – and not for cache keys passed to use() either. With server rendering it also requires the server and client to render an identical component tree, since the generated ids are derived from each component's position within it; mismatched trees produce mismatched ids. (It currently can't be called inside an async Server Component at all, though that's less likely to come up in the client-rendered demos on this page.)

The same PasswordField component is rendered twice below; each instance's useId() call still returns a different string, so the two aria-describedby/id pairs never collide. (The visible "id = ..." text is only there to make the generated string visible for this demo — you wouldn't normally render it.)
RESETRUNFULL
<!DOCTYPE html><html><head>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head><body>
<div></div>
<script type="text/babel">
function PasswordField() {
  const passwordHintId = React.useId();
  return (
    <div>
      <label>Password: <input type="password" aria-describedby={passwordHintId} /></label>
      <p id={passwordHintId}>id = {passwordHintId}</p>
    </div>
  );}
function App() {
  return (
    <React.Fragment>
      <PasswordField />
      <PasswordField />
    </React.Fragment>
  );}
const root = ReactDOM.createRoot(document.querySelector("div"));
root.render(<App/>);
</script>
</body></html>

A single call can also seed several related ids at once, by appending different suffixes to it:

One useId() call, two derived ids — htmlFor and id still line up correctly for both fields.
RESETRUNFULL
<!DOCTYPE html><html><head>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head><body>
<div></div>
<script type="text/babel">
function NameForm() {
  const id = React.useId();
  return (
    <form>
      <label htmlFor={id + '-firstName'}>First Name: </label>
      <input id={id + '-firstName'} type="text" />
      <br/>
      <label htmlFor={id + '-lastName'}>Last Name: </label>
      <input id={id + '-lastName'} type="text" />
    </form>
  );}
const root = ReactDOM.createRoot(document.querySelector("div"));
root.render(<NameForm/>);
</script>
</body></html>