Volume 3

Q182: useId, Accessibility and Stable Generated IDs

Difficulty: IntermediateFrequency: MediumAnswer time: 6-10 minutes

What Interviewers Want To Evaluate

This question checks whether you can generate stable IDs in server-rendered React without hydration problems. Interviewers want useId, accessibility relationships, label/input links, aria-describedby, SSR stability, list keys distinction, and common ID mistakes.

Senior answers should connect generated IDs to accessible component APIs.

Short Interview Answer

useId generates a stable unique ID that is consistent between server and client rendering. It is useful for accessibility relationships like labels, descriptions, error messages, tabs, and form fields. It should not be used for list keys because keys must come from data identity. useId solves generated DOM ID stability, not data identity.

Detailed Interview Answer

Many accessible components need matching IDs.

const id = useId();

return (
  <>
    <label htmlFor={id}>Email</label>
    <input id={id} />
  </>
);

The label and input are now connected.

This matters for screen readers, click behavior, and form usability.

Why Not Math.random

Bad:

const id = `field-${Math.random()}`;

This can differ between server render and client hydration.

It also changes between renders if not stored carefully.

Hydration and accessibility both suffer.

Why Not Date.now

Date.now() has the same problem.

The server output and client output may not match.

Stable generated IDs should come from React when React owns the component tree.

Accessibility Relationships

IDs power many relationships.

label htmlFor input id
aria-describedby
aria-labelledby
aria-controls
aria-errormessage
tabs and tab panels
accordion trigger and content
dialog title and description

Reusable components should make these relationships easy to create correctly.

Error Message Example

function TextField({ label, error }) {
  const inputId = useId();
  const errorId = `${inputId}-error`;

  return (
    <div>
      <label htmlFor={inputId}>{label}</label>
      <input
        id={inputId}
        aria-invalid={Boolean(error)}
        aria-describedby={error ? errorId : undefined}
      />
      {error ? <p id={errorId}>{error}</p> : null}
    </div>
  );
}

The error is programmatically associated with the input.

Prefixes

IDs can be composed with suffixes.

const baseId = useId();
const triggerId = `${baseId}-trigger`;
const panelId = `${baseId}-panel`;

This is useful for compound components like tabs, accordions, and dialogs.

useId Is Not For Keys

Do not use useId for list keys.

Bad:

items.map((item) => <Row key={useId()} item={item} />);

Keys should come from stable data identity.

items.map((item) => <Row key={item.id} item={item} />);

useId is for DOM IDs, not reconciliation identity.

Server Rendering

useId is designed to be stable across server and client output.

This prevents generated IDs from causing hydration mismatches.

It is especially valuable in component libraries used across SSR and client-rendered apps.

Component Library Design

Reusable fields should allow:

auto-generated id by default
explicit id override when needed
stable derived ids for help and error text
consistent aria relationships
no random IDs during render

The consumer should not need to wire every ID manually for common cases.

Common Mistakes

A common mistake is using useId as a key.

Another mistake is generating random IDs during render.

Another mistake is rendering an error message without connecting it to the field.

Another mistake is changing IDs when props change.

Another mistake is forgetting ID relationships inside compound components.

Senior Trade-Offs

useId is small, but it enables reliable accessible abstractions.

The senior angle is not memorizing the hook. It is designing components where labels, descriptions, errors, tabs, and panels remain connected even when reused many times on one page.

Debugging Workflow

When accessibility relationships fail:

inspect label htmlFor and input id
check aria-describedby values
verify referenced element exists
check duplicate ids
check server/client mismatch warnings
avoid random id generation
test repeated component instances
test screen reader field announcement

Interview Framing

Define useId, show form and error examples, clarify SSR stability, and explicitly say it is not for list keys.

Revision Notes

useId creates stable DOM IDs for accessibility relationships. Data keys still belong to data.

Key Takeaways

Stable IDs are accessibility infrastructure.

Follow-Up Questions

  1. What does useId generate?
  2. Why is it SSR-safe?
  3. Why not use Math.random?
  4. What accessibility relationships need IDs?
  5. How do you connect an error to an input?
  6. Can consumers override generated IDs?
  7. Why is useId bad for keys?
  8. What should keys use instead?
  9. How do compound components use IDs?
  10. How do you detect duplicate IDs?

How I Would Answer This In A Real Interview

I would say useId creates stable IDs for DOM accessibility relationships such as labels, descriptions, errors, tabs, and dialogs. I would use it inside reusable components, allow explicit overrides, and avoid using it for list keys because keys must represent data identity.