WCAG 2.2 Success Criterion 4.1.2 requires that for every user-interface component, the name and role can be programmatically determined; states, properties, and values that the user can set can also be programmatically determined; and notification of changes to these items is available to user agents and assistive technologies. The normative text reads:
“For all user interface components … the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.”
This is the single most consequential SC for screen-reader users. A button with no accessible name reads as “button” with no further context. Pressing it is an act of faith.
The failing patterns
<!-- icon-only button: no accessible name -->
<button class="icon-btn">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 6h18..." />
</svg>
</button>
<!-- custom dropdown rolled with divs -->
<div class="dropdown" onclick="toggle()">
Choose option
<div class="menu">...</div>
</div>
<!-- input without label -->
<input type="email" placeholder="Email" />
Each of these renders something visible but exposes no name and/or no role to
the accessibility tree. axe-core flags all three with button-name,
aria-required-children, and label rules respectively.
The fix — semantic first, ARIA second
The first rule of ARIA is: don’t use ARIA. Native HTML elements come with the right name, role, and value behaviours for free.
<!-- icon-only button — accessible name via aria-label -->
<button class="icon-btn" aria-label="Delete item">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M3 6h18..." />
</svg>
</button>
<!-- dropdown — use <select> or a combobox pattern -->
<label for="country">Country</label>
<select id="country" name="country">
<option value="">Select...</option>
<option value="se">Sweden</option>
<option value="de">Germany</option>
</select>
<!-- input — explicit label association -->
<label for="email">Email address</label>
<input id="email" type="email" name="email" required />
When you do need ARIA — for example because the design specifies a disclosure pattern that maps onto no native HTML element — pick a canonical recipe from the WAI-ARIA Authoring Practices Guide rather than inventing one.
Accessible name precedence
The accessible name comes from the first applicable source in this order (simplified from the Accessible Name and Description Computation 1.2):
aria-labelledbyreferencing visible textaria-label- The element’s content (e.g. button’s child text)
<label for>for form controls;<title>for SVG;altfor imagesplaceholder(last resort, often inaccessible — do not rely on it)
Common rule: prefer visible text to invisible labels. A button reading
“Add to cart” with no aria-label is easier to test, easier to translate,
and degrades gracefully if a future redesign hides the label.
State and value — the silent half
Name and role get all the attention, but 4.1.2 requires state and value too.
A toggle button must expose aria-pressed. A disclosure must expose
aria-expanded. A custom checkbox must expose aria-checked. A combobox
must expose aria-activedescendant or focus the selected option directly.
<button
type="button"
aria-pressed="false"
aria-label="Mute audio"
onclick="toggleMute(this)">
<svg aria-hidden="true">...</svg>
</button>
When toggleMute flips state, the script must update both the visual
appearance and the aria-pressed value. JAWS, NVDA, and VoiceOver all
announce the change as “pressed” or “not pressed”.
How to test
Automated — axe-core’s button-name, link-name, label,
aria-allowed-attr, aria-required-attr, and role-img-alt rules cover
the most common 4.1.2 failures:
const results = await new AxeBuilder({ page })
.withRules([
"button-name",
"link-name",
"label",
"aria-allowed-attr",
"aria-required-attr",
"input-button-name",
])
.analyze();
Manual screen-reader smoke — nothing replaces this. Per element:
- NVDA + Firefox on Windows (open-source baseline).
- VoiceOver + Safari on macOS or iOS (user share is large in EU).
- JAWS + Chrome on Windows for enterprise QA where contracts require it.
For each element, listen for the announcement: it should include name, role, and state in that order. “Mute audio, button, not pressed” is correct. “Button” alone fails 4.1.2.
When this is hard
- Translated UIs.
aria-labelis invisible — a translator may miss it. Prefer<span class="visually-hidden">text content, which is visible to translation tooling but hidden visually. - Dynamic state. Live regions with
aria-live="polite"announce state changes for sighted users who navigate by mouse and may have cognitive load. Avoidassertiveexcept for genuine errors — it interrupts whatever the screen reader is reading. - Custom comboboxes. The combobox pattern changed materially between ARIA 1.1 and 1.2; pick the 1.2 pattern and pin to it. Screen-reader support varies; test on at least two combinations.
Cross-links
- Reference: WCAG 4.1.2 explained on w3c.wiki for normative depth.
- Adjacent: How to fix WCAG 1.3.1 info and relationships for landmark and heading semantics.
- Testing: How to test with NVDA for the manual smoke procedure.
- Framework patterns: React 19 forms accessibility for
aria-invalid,aria-errormessage, and server-action error focus management.
If you remember one rule: every interactive thing has a name a screen
reader can speak; non-interactive decoration is hidden with aria-hidden.
That single principle resolves perhaps 80% of 4.1.2 violations in the wild.