ProgramGuru

Accessible React Interfaces

Accessible React Interfaces

A student uses a keyboard to complete a form, while another student uses a screen reader. A visually attractive field with only placeholder text may leave both unsure what information to enter or whether submission succeeded.

An accessible interface lets people perceive, understand and operate it using different abilities and tools. React renders HTML, so ordinary semantic elements, labels and keyboard behavior remain the foundation.

  1. Name controls

    Associate a persistent label with each field.

  2. Support operation

    Use native buttons and predictable keyboard behavior.

  3. Explain results

    Provide feedback that can be understood without color alone.

Follow the user’s interaction

  1. Control

    A label identifies the student-name field.

  2. Action

    A native submit button starts the greeting.

  3. Feedback

    A status message describes the result.

Read this relationship alongside the complete code. This diagram explains the flow; it does not execute React.

Start with what the control means

A label names a field. In JSX, htmlFor points to the input’s id, allowing the browser to connect them. A visible label remains after the user types; a placeholder may disappear. A real button supports keyboard activation without inventing click handlers on a generic div.

The example uses useId to generate linked IDs safely when more than one instance of a component appears. aria-describedby points to supplementary instructions, while the label remains the control’s name. React’s input documentation explains label associations, and the useId reference describes generated accessibility IDs.

A labeled form with visible feedback

The field starts with Maya. Create greeting submits the form, prevents this local demo from navigating away and sets a result message. The status region is present from the initial render so later changes have a stable place to appear. It is not a replacement for good wording or manual accessibility checks.

Try entering a different name and submitting by keyboard as well as by mouse. The instructions are linked to the input, and the outcome does not depend on the student recognizing a particular color.

React · JavaScript with JSX

import React, { useId, useState } from 'react';
export default function App() {
  const nameId = useId();
  const helpId = useId();
  const [name, setName] = useState('Maya');
  const [message, setMessage] = useState('No greeting yet');
  function submit(event) {
    event.preventDefault();
    setMessage(name.trim() ? 'Hello, ' + name.trim() : 'Enter a student name');
  }
  return <main><h1>Student greeting</h1><form onSubmit={submit}>
    <label htmlFor={nameId}>Student name</label>
    <input id={nameId} aria-describedby={helpId} value={name}
      onChange={event => setName(event.target.value)} />
    <p id={helpId}>Use the name shown on the class register.</p>
    <button type="submit">Create greeting</button>
  </form><p role="status">{message}</p></main>;
}

Expected screen and behavior

Initially: Student greeting, Student name field containing Maya, instructions, Create greeting and No greeting yet.
Submitting displays Hello, Maya.

Semantics survive the React render

React · the label, field and feedback

import React, { useId, useState } from 'react';
export default function App() {
  const nameId = useId();
  const helpId = useId();
  const [name, setName] = useState('Maya');
  const [message, setMessage] = useState('No greeting yet');
  function submit(event) {
    event.preventDefault();
    setMessage(name.trim() ? 'Hello, ' + name.trim() : 'Enter a student name');
  }
  return <main><h1>Student greeting</h1><form onSubmit={submit}>
    <label htmlFor={nameId}>Student name</label>
    <input id={nameId} aria-describedby={helpId} value={name}
      onChange={event => setName(event.target.value)} />
    <p id={helpId}>Use the name shown on the class register.</p>
    <button type="submit">Create greeting</button>
  </form><p role="status">{message}</p></main>;
}

React updates the message text when state changes; the browser still sees a form, labeled input, button and status region. Accessibility comes from those resulting elements and relationships. Component names such as AccessibleField are helpful to developers, but their names alone do not improve the HTML.

role="status" expresses advisory feedback, commonly announced politely by assistive technology. Keep messages short and relevant. Avoid making every small visual update an alert, since constant interruption can make an application harder to use. A critical field error may require a different presentation and focused guidance.

Test the entire task, not one attribute

Start without a mouse. Tab to the input and button, type a name, and submit. Focus should remain visible and move in a sensible order. A custom control that responds only to clicks fails this task even if it has an aria-label. Prefer native elements before attempting to reproduce their keyboard behavior.

Check heading order, zoom and narrow layouts as well. A teacher enlarging text should still find the submit action without losing the field label. Images that convey information need appropriate alternatives; decorative images need a different policy. These checks evaluate the page people use, not just the presence of selected attributes.

Worked practice: explain an empty submission

Start with an empty field and submit. Keep the label and instructions visible, then display an actionable message. The complete worked answer uses the same trimming rule, so whitespace alone does not produce an apparently successful greeting.

Enter a student name appears in the result region. The form does not clear the user’s draft automatically. A larger form should associate errors with the affected fields and decide whether moving focus to an error summary helps users; this small one-field example only shows the message mechanism.

React · JavaScript with JSX

import React, { useId, useState } from 'react';
export default function App() {
  const nameId = useId();
  const helpId = useId();
  const [name, setName] = useState('');
  const [message, setMessage] = useState('No greeting yet');
  function submit(event) {
    event.preventDefault();
    setMessage(name.trim() ? 'Hello, ' + name.trim() : 'Enter a student name');
  }
  return <main><h1>Student greeting</h1><form onSubmit={submit}>
    <label htmlFor={nameId}>Student name</label>
    <input id={nameId} aria-describedby={helpId} value={name}
      onChange={event => setName(event.target.value)} />
    <p id={helpId}>Use the name shown on the class register.</p>
    <button type="submit">Create greeting</button>
  </form><p role="status">{message}</p></main>;
}

Expected screen and behavior

Initially: an empty Student name field and No greeting yet.
Submitting displays Enter a student name.

ARIA does not repair an unclear interaction

Do not add a button role to a div and assume it now has every native button behavior. Do not remove focus outlines without an equally visible replacement. Disabled controls also need an understandable explanation when users cannot complete a task.

A labeled input is useful evidence, but it is not proof that the whole page works with every assistive technology. Automated checks can reveal selected problems; manual keyboard and screen-reader checks remain valuable. For modal dialogs, additional focus entry, containment, escape and return behavior is required beyond placing a panel on top of the page.

Check the label

What connects this visible label to its input?
Choose one answer. You can try again.

Answer and explanation

htmlFor matches the input’s id.

Correct. This creates the programmatic association.

Apply native behavior

Why prefer a button to a div with an onClick for submission?
Choose one answer. You can try again.

Answer and explanation

Native buttons supply established keyboard and semantic behavior.

Correct. The control already expresses an operable action.

Build a task people can operate and understand

Use native controls, persistent labels, meaningful instructions and visible result messages. Check keyboard focus and complete tasks in the actual page. React can preserve these relationships as values change, but attributes alone do not establish a usable experience.

Apply these checks to form validation and tests that query controls by their accessible names.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Error Boundaries and Suspense →