useId

Stable, Unique IDs for Accessible Markup

useId

`useId` returns a unique string per component instance — perfect for pairing labels with inputs without hardcoding `id`s.

3 min read Level 1/5 #react#useId#accessibility
What you'll learn
  • Use `useId` to link `<label>` and `<input>`
  • Avoid id collisions in reusable components

For accessible forms, <label htmlFor> needs to match an <input id>. Hardcoding an id breaks when you mount the same component twice on the same page — two inputs end up with the same id.

useId generates a unique string per component instance.

The Shape

function NameField() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Name</label>
      <input id={id} type="text" />
    </>
  );
}

Mount NameField twice — each gets its own unique id, labels pair correctly with their inputs.

Multiple Inputs Per Component

Use the returned id as a prefix:

function NamePair() {
  const id = useId();
  return (
    <>
      <label htmlFor={`${id}-first`}>First</label>
      <input id={`${id}-first`} />

      <label htmlFor={`${id}-last`}>Last</label>
      <input id={`${id}-last`} />
    </>
  );
}

NOT for List Keys

useId generates ids for DOM attributes. For React keys in a .map, you need an id from your data — not useId.

// ✗ useId returns the SAME value across renders for a given instance
// (and it doesn't distinguish list items)
{items.map(item => <Row key={useId()} item={item} />)}

// ✓ Use the item's id
{items.map(item => <Row key={item.id} item={item} />)}

Plus, calling a hook inside .map violates the rules of hooks.

SSR

useId produces ids that match on server and client — important for server-side rendering. Don’t hand-roll Math.random() ids; you’ll get hydration mismatches.

Up Next

Hooks for telling React “this update isn’t urgent, take your time”.

useDeferredValue →