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
- Open Chrome DevTools → Accessibility pane. For each control, confirm Name, Role, and state fields are populated correctly.
- In your screen reader, tab through and listen. Every control should announce "name, role" (e.g. "Save, button").
- 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-labelon a native element replaces the element's name. Use sparingly; prefer visible text.- Don't put
aria-labelon 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.
Related rules
- WCAG 2.5.3 Label in Name — the visible label must appear in the accessible name.
- WCAG 1.3.1 Info and Relationships — semantic markup.
- axe: button-name, link-name, select-name, input-button-name, aria-command-name, aria-toggle-field-name, frame-title.