Skip to main content
WCAG Patterns

WCAG 4.1.2 · Level A · WCAG 2.0

Name, Role, Value

For all UI components, the name and role must be programmatically determinable; states, properties, and values can be set programmatically.

Principle
Robust
Guideline
Compatible
Level
A
Added in
WCAG 2.0

What it really means

For every UI component — native or custom — assistive tech must be able to read three things:

  • Name — what is this? "Save", "Email", "Open menu".
  • Role — what kind of thing? button, checkbox, tab, dialog.
  • Value / state — is it checked? pressed? expanded? valid?

Native HTML gives you all three for free. <button>Save</button> has name "Save", role "button", and pressed state via aria-pressed if you need it. The moment you swap <button> for <div>, you owe ARIA the pieces it lost.

This is the broadest and most-cited AA criterion. Every accessible-name axe rule — button-name, link-name, input-button-name, select-name, aria-input-field-name, aria-command-name, frame-title — rolls up here.

Who it helps

  • Screen-reader users, who hear the name followed by the role.
  • Voice-control users, who speak the visible name to click it (which requires the visible name and the accessible name to match — see 2.5.3 Label in Name).
  • Testing tools — Playwright's getByRole, Testing Library, and Cypress queries all rely on the accessibility tree this criterion enforces.

How to test

  1. Open Chrome DevTools → Accessibility pane. For each control, confirm Name, Role, and state fields are populated correctly.
  2. In your screen reader, tab through and listen. Every control should announce "name, role" (e.g. "Save, button").
  3. Run axe DevTools — all the rules named above trigger under 4.1.2.

A failing pattern

// Clickable div — no role, no keyboard, no accessible name for AT.
<div onClick={handleSave}>💾</div>
 
// Custom switch — no role, no state.
<div
  className={on ? "toggle on" : "toggle off"}
  onClick={() => setOn((v) => !v)}
/>
 
// Select without a label.
<select>
  <option>English</option>
  <option>Swedish</option>
</select>

A passing pattern

Wherever possible, use the native element. Native beats ARIA every time.

// Native button — name from content, role from element, state inferred.
<button type="button" onClick={handleSave}>
  Save
</button>
 
// Native switch via checkbox + role. Still needs a label.
<label>
  <input
    type="checkbox"
    role="switch"
    checked={on}
    onChange={(e) => setOn(e.target.checked)}
  />
  Notifications
</label>
 
// Select with a label.
<label htmlFor="locale">Language</label>
<select id="locale">
  <option>English</option>
  <option>Swedish</option>
</select>

When you must build from scratch, ARIA supplies the missing pieces:

function ToggleButton({
  pressed,
  onPress,
  children,
}: {
  pressed: boolean;
  onPress: () => void;
  children: React.ReactNode;
}) {
  return (
    <button
      type="button"
      aria-pressed={pressed}
      onClick={onPress}
    >
      {children}
    </button>
  );
}

Custom comboboxes, trees, grids, and menus have well-documented ARIA patterns — always start from the WAI-ARIA Authoring Practices rather than improvising.

Notes and edge cases

  • aria-label on a native element replaces the element's name. Use sparingly; prefer visible text.
  • Don't put aria-label on elements that don't accept it (headings, paragraphs, generic divs without a role). The axe: aria-prohibited-attr rule flags this.
  • Icon-only controls always need an accessible name. The most common source of 4.1.2 failures on the web.