React Best Practices

Chapter 6 · Practice 2 of 4

Give every form control a visible label

From Accessibility

Why it matters

Every input needs a label that is connected to it, either by wrapping the input in <label> or by matching htmlFor to the input's id.

  • Placeholders are not labels: they disappear when the user types and often have low contrast.
  • Generate ids with useId so they stay unique when the component renders several times and match between server and client.
  • Connect error messages with aria-describedby so screen readers read them with the field.

Example

TSX
function EmailField({ error }: { error?: string }) {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Email</label>
      <input id={id} name="email" aria-describedby={error ? id + '-err' : undefined} />
      {error && <p id={id + '-err'}>{error}</p>}
    </>
  );
}