Building Accessible Forms in React with useId
When building forms in React, accessibility should never be an afterthoughtâitâs a fundamental part of delivering a high-quality user experience. One of the simplest but most critical practices is making sure every input field is properly associated with its label. This ensures users with screen readers or motor impairments can interact with your form just as effectively as everyone else.
But when youâre creating reusable components, this simple rule gets a bit trickier. Letâs explore whyâand how Reactâs useId hook helps solve the problem.
Each input element in your form should have a unique id, and its corresponding label should use the htmlFor attribute (or for in plain HTML) pointing to that ID:
<label for="email">Email:</label><input id="email" type="email" />
This connection:
However, when youâre building reusable components like <Field />, hardcoding IDs isnât an optionâyouâd risk duplicating IDs if the component is used multiple times. And generating random IDs comes with its own problems, especially in server-rendered (SSR) React apps.
useId đŻReactâs useId hook solves this problem elegantly. It generates a unique and stable ID string for each instance of a component, and itâs consistent between server and client, making it SSR-safe.
Hereâs how you can use it:
import { useId } from 'react'function FormField() {const id = useId()return (<div><label htmlFor={id}>Name:</label><input id={id} type="text" /></div>)}
Unlike useState or useEffect, useId doesnât accept any arguments and doesnât return a setter. It just gives you a consistent, unique ID you can rely on throughout the componentâs lifecycle.
<Field /> ComponentLetâs say youâre building a generic Field component for your app. You want to:
Hereâs how you could implement that:
function Field({ label, id: customId, name, ...props }) {const generatedId = useId()const id = customId ?? generatedIdreturn (<div><label htmlFor={id}>{label}</label><input id={id} name={name} {...props} /></div>)}
Now, if a parent passes a specific id, your component will use it. Otherwise, it falls back to a safely generated unique ID.
â This means:
useId is for DOM element associations onlyâlike linking a label to an input or associating aria-describedby attributes.Incorrect:
items.map(item => <li key={useId()}>{item}</li>) // â BAD
Correct:
items.map(item => <li key={item.id}>{item.name}</li>) // â GOOD
identifierPrefix Thing?The useId hook also works with an optional identifierPrefix feature in advanced cases (mostly when working with multiple React roots or frameworks that integrate React). You probably wonât need itâbut hereâs the doc if youâre curious.
Accessibility is about inclusion, and small details like proper id/label connections can make a big difference. With useId, React gives us a powerful, ergonomic tool to ensure our forms are accessible, even when abstracted into reusable components.
Next time youâre building a form field component, give useId a spinâitâs the simplest way to make your UI just work, for everyone.
đšâđ» Want to dig deeper into accessibility and form design in React? Check out the official React docs on useId.
Letâs keep building better web experiencesâone unique ID at a time.