4.1.2 Name, Role, Value

WCAG 2.2 · 4.1.2 A Robust

What it requires

For every user-interface component — links, buttons, form fields, and custom widgets such as menus, tabs, sliders, and toggles — the following must be exposed to assistive technology so it can be programmatically determined and, where the user can change it, set:

  • Name — the accessible label that identifies the control (for example, “Search” on a button).
  • Role — what kind of component it is (button, checkbox, tab, etc.).
  • Value & states — the current value and any states or properties, such as checked, expanded, selected, or disabled, kept in sync as they change.

Native HTML elements provide name, role, and value for free. The criterion fails most often when controls are built from generic <div> or <span> elements without the ARIA roles, names, and states needed to convey the same information.

  • Screen reader users — hear an unlabeled or mis-typed control (“clickable” instead of “Submit, button”), so they cannot tell what it is or its state.
  • Voice-control users — cannot target a control by name if it has no accessible name to say.
  • Switch and other AT users — rely on role and state to understand and operate custom widgets.

How to detect it

Concrete checks for Name, Role, Value
Check How Catches
Accessible name Inspect each control in the browser accessibility tree; confirm a meaningful name. Icon-only buttons, unlabeled inputs.
Correct role Verify custom widgets expose a valid role, not a bare div/span. “Fake” buttons and links built from generic elements.
State sync Toggle the control; confirm aria-expanded, aria-checked, etc. update. States that never change or are missing.
Screen reader pass Navigate with NVDA/VoiceOver; listen for name + role + state on each control. Most real-world failures, in context.
Automated scan Run axe / Lighthouse for missing names and invalid ARIA. Many name/ARIA issues; cannot judge wrong-but-present roles.

How to fix it

  1. Use the native element first — <button>, <a href>, <input> — so name, role, and value come built in.
  2. Give every control an accessible name: visible text, a <label>, or aria-label / aria-labelledby when no visible text exists.
  3. When you must build a custom widget, add the correct ARIA role and follow the matching ARIA Authoring Practices pattern.
  4. Expose and update state with the right ARIA attributes (aria-expanded, aria-checked, aria-selected) whenever it changes.
  5. Re-test with a screen reader to confirm name, role, and state are all announced.
<button type="button" aria-expanded="false" aria-controls="menu">
  Account menu
</button>
<ul id="menu" hidden>
  <li><a href="/profile">Profile</a></li>
</ul>

The native <button> supplies role and name; aria-expanded carries the state and is updated in script as the menu opens and closes.

Copy-paste tests

Automated coverage

These axe-core rules flag failures of this criterion: area-alt, aria-allowed-attr, aria-braille-equivalent, aria-command-name, aria-conditional-attr, aria-deprecated-role, aria-hidden-body, aria-hidden-focus, aria-input-field-name, aria-prohibited-attr, aria-required-attr, aria-roledescription, aria-roles, aria-tab-name, aria-toggle-field-name, aria-tooltip-name, aria-valid-attr, aria-valid-attr-value, button-name, duplicate-id-aria, frame-title, frame-title-unique, input-button-name, input-image-alt, label, link-name, nested-interactive, select-name, summary-name. Run them via the axe DevTools browser extension or axe-core in CI. Automated tools only catch some failures.

Run this in the browser console

name-role-value-check.js
// Read-only: surface interactive elements that may lack an accessible name.
const sel = 'button, a[href], input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="checkbox"], [role="switch"], summary, iframe';
const suspect = [...document.querySelectorAll(sel)].filter(el => {
  const name = (el.getAttribute('aria-label')
    || (el.getAttribute('aria-labelledby') && 'labelledby')
    || el.textContent.trim()
    || el.getAttribute('title')
    || el.getAttribute('alt')
    || (el.labels && el.labels.length && 'label')
    || (el.matches('input,select,textarea') && el.placeholder));
  return !name;
});
suspect.forEach(el => el.style.outline = '2px solid red');
console.table(suspect.map(el => ({ tag: el.tagName, role: el.getAttribute('role'), type: el.type, html: el.outerHTML.slice(0, 80) })));
console.log('Likely unnamed interactive elements:', suspect.length);

What to check manually: confirm each accessible name actually describes the control's purpose (not just that one exists), and that custom widgets expose the correct role and report state changes (expanded, checked, selected) to a screen reader as you operate them.