DevPath · Learn to code ESPTEN

Professional React: TypeScript, SSR, and accessibility

Accessibility (a11y) in React

Accessibility: for everyone, not optional

Accessibility (abbreviated a11y: "a", 11 letters, "y") is building interfaces that anyone can use, including those who navigate with a screen reader, with the keyboard only, or with low vision. It's not an extra: it's software quality (and, often, a legal requirement).

The good news: 90% of accessibility is well-done HTML.

1) Semantic HTML

Use the correct element for each thing. A <button> is already focusable, clickable with Enter/Space, and announced as a button; a <div onClick> is none of that.

// ❌ Bad: a div is not keyboard accessible nor announced as a button
<div onClick={close}>Close</div>

// ✅ Good
<button onClick={close}>Close</button>

Same with <nav>, <main>, <header>, <ul>/<li>, headings <h1>…<h6> in order: they give structure that assistive technologies understand.

2) Labels associated with their fields

Every form control needs an associated label. The robust way is <label htmlFor={id}> pointing to the field's id. In JSX it's written htmlFor (not for, which is a reserved word in JS), but in the real DOM it becomes the for attribute.

<label htmlFor="email">Email</label>
<input id="email" type="email" />

By associating them: the screen reader announces the label when the field is focused, and clicking the label focuses the input (better usability for everyone).

3) ARIA, only when needed

The ARIA attributes (aria-*) add semantic information that HTML by itself doesn't express. The first rule of ARIA is: don't use ARIA if a native element already does the job. But it's very useful for nuances:

<input aria-required="true" aria-invalid={hasError} />
<button aria-label="Close dialog">✕</button>     {/* button with only an icon */}
<nav aria-label="Main">…</nav>

4) Focus and keyboard navigation

Many people navigate without a mouse. Make sure that:

5) Unique IDs with useId

Associating label and input by id has a problem in React: if the component is used multiple times on the page, a fixed id ("email") would be duplicated, and ids must be unique. The solution is the useId hook, which generates a unique and stable identifier (it also works well with SSR/hydration):

function Field({ label }) {
  const id = React.useId();
  return (
    <p>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </p>
  );
}

Important: useId is for accessibility ids (linking elements), not for list keys. Reuse the same id (with suffixes if you need several: ${id}-error) to relate elements within the same component.

In this runtime's exercises, use React.useId() (with the React. prefix) to generate the id.

Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
← Server rendering: CSR, SSR, SSG, and Server ComponentsView the module →