Product design skills

references/naming.md

A supporting file of the ultra11y skill.

Accessible names — how one is computed, and how to get it right

Most of what an audit calls "4.1.2" is a naming problem. This page is the reference for what a name IS, so a verdict rests on the computation rather than on the markup's appearance.

The precedence chain (accname), highest first

A control's accessible name is the FIRST of these that yields non-empty text:

#SourceNotes
1aria-labelledbyWins over everything, including visible content. Concatenates the text of every id, in the order listed. A dangling id contributes nothing — an all-dangling list leaves the element unnamed.
2aria-labelReplaces the visible text entirely. This is what breaks 2.5.3 (see below).
3Native host semantics<label for> / wrapping <label> for form fields, alt for <img>, <legend> for <fieldset>, <caption> for <table>, <figcaption> for <figure>, value for <input type="submit">.
4Text contentFor elements whose role allows "name from content": button, link, heading, menuitem, tab, option… NOT for a <div> or an <input>.
5titleLast resort. Hover-only: absent on touch, unevenly announced. A name that exists but should not be the plan.

Two consequences worth stating explicitly, because both produce confident wrong verdicts:

  • A name that exists is not a name that works. aria-label="Button" satisfies "has a name" and helps nobody. Judge relevance, not presence.
  • The chain stops at the first hit. <button aria-label="Close">Save</button> is named "Close". The visible word "Save" never enters the computation.

2.5.3 Label in Name — the rule voice control depends on

When a control shows visible text, that text must be contained in the accessible name. A speech-input user says what they see; if the name differs, the command silently does nothing.

<!-- ✗ the spoken "click Send" matches nothing -->
<button aria-label="Submit form">Send</button>

<!-- ✓ the visible text is contained, and comes first -->
<button aria-label="Send message to support">Send</button>

<!-- ✓ best: no override at all — the text names the button -->
<button>Send</button>

Rule of thumb: if you find yourself writing aria-label on an element that already shows text, you are usually about to break 2.5.3. Prefer aria-describedby for the extra detail. Engine rule: label-in-name-mismatch.

Visually-hidden text — the CSS that actually works

Naming an icon control with text only screen readers reach is the native-first answer. display:none and visibility:hidden remove the text from the accessibility tree too; text-indent:-9999px breaks in RTL; font-size:0 is unreliable.

/* The only clipping recipe that survives every engine, and stays focusable. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Skip links: visible again once focused. */
.visually-hidden:focus-visible {
  position: static;
  width: auto;
  height: auto;
  clip-path: none;
  white-space: normal;
}

Naming non-text content

<!-- Informative image: describe the MEANING in context, not the picture -->
<img src="chart.png" alt="Sales up 25% in Q3 2025">

<!-- Decorative image: an explicit empty alt, never a missing one -->
<img src="divider.png" alt="">

<!-- Icon control: the name describes the ACTION, not the glyph -->
<button aria-label="Delete this comment"><svg aria-hidden="true" focusable="false">…</svg></button>

<!-- Inline SVG treated as an image -->
<svg role="img" aria-labelledby="logo-title"><title id="logo-title">Acme</title>…</svg>

<!-- Decorative SVG: hide it and make sure it is not a tab stop in IE-era engines -->
<svg aria-hidden="true" focusable="false">…</svg>

<!-- Figure: the caption is visible text AND the accessible name -->
<figure>
  <img src="plan.png" alt="Ground floor: reception, then three meeting rooms along a corridor">
  <figcaption>Ground floor plan</figcaption>
</figure>

<!-- Abbreviation: expand on first use in text; title alone is hover-only -->
<p>The <abbr title="World Wide Web Consortium">W3C</abbr> publishes WCAG.</p>

Charts. An alt naming the chart ("Sales chart") is not an alternative — it names the container and drops the data. Either put the figures in the alt, or point at an equivalent table nearby. Engine rule: chart-no-accessible-name.

What the engine settles, and what stays yours

Decided by the engineYours to adjudicate
Name absent (img-alt-missing, button-empty-name, link-empty-name, control-label-missing, iframe-title-missing, icon-only-control-unnamed)Whether the name is relevant (1.1.1, 2.4.4, 2.4.6) — the engine cannot read meaning
Name provided only by title (control-name-title-only, img-alt-missing.title-only as a recommendation)Whether an alt="" image is genuinely decorative
Visible text not contained in the name (label-in-name-mismatch)Whether an icon font makes the DOM text invisible (then 2.5.3 does not apply)
Names prohibited by the role (aria-prohibited-attr)Whether two same-named links share a destination (2.4.4)

Cross-file: under --graph the engine resolves a name passed as a prop into a component, and flags one that gets lost on the way (cross-prop-drilled-name-lost). See references/cross-file.md.

On this page