Skip to content
svelte-vitals
English
Esc
navigateopen⌘Jpreview
On this page

a11y/accessible-name · Interactive element has no accessible name

A button, link, image button, or iframe needs a way to compute its accessible name.

Severity: warning · Category: a11y

What it checks

Flags a <button>, <a href="…">, <input type="image">, or <iframe> with no computable accessible name.

Any of the following, if present, is a name source, and the element is not flagged:

  • A non-whitespace text descendant, e.g. <button>Save</button>.
  • An aria-label, aria-labelledby, or title attribute, either a literal non-empty value or any expression (its rendered value is unknowable, but the attribute’s presence is enough). title counts because the name computation uses it as a last resort, but it is a poor way to name a control: it is unavailable to touch users, keyboard users, and many screen-reader and magnifier users. Prefer visible text or aria-label.
  • A descendant <img> with an alt, either a non-empty literal or any expression, on the same footing as aria-label above.
  • For <input type="image">, its own alt, under the same rule.
  • A <label> that names it: one wrapping it, or one pointing for at its id. This step comes ahead of the element’s own subtree in the name computation, and applies to <button> and <input type="image"> only; <a> has no such step. It counts only when the label itself contributes something: a label that is provably empty leaves the control unnamed and still reported, and a wrapping label reaches only the first labelable element inside it, so a second control in the same label is judged on its own. The for route is same-file only; a label in another component is a known limitation.

Not flagged, even with no name source found:

  • An element whose content is unknowable: any {expression} child, a component child, {@render …}, {@html …}, a <slot> or <svelte:fragment> (its content comes from the parent), a hyphenated custom element (its shadow root may supply content), or a spread attribute on the element itself. The rule only flags what it can prove is unnamed; it never guesses at dynamic content.
  • Namespace is not tracked here: an <a href> inside <svg> is judged as the HTML <a>, so an SVG link without a computable accessible name (no text descendant, aria-label, aria-labelledby, or <title>) is reported. That is the same thing the SVG accessibility guidance asks for, so the verdict stands even though the reason differs.
<button></button>
<a href="/x"><img src="i.png" /></a>

The iframe arm

An <iframe> is named by its title, aria-label, or aria-labelledby only, either a literal non-empty value or any expression (unknowable → assumed to name, as above). A blank literal title="" computes no name and is flagged. Its children never count: iframe content is fallback for browsers without frames, not rendered content, so there is no name-from-content step, and a <label> cannot name it either (an iframe is not labelable).

Not flagged, specific to this arm:

  • An iframe with aria-hidden="true", a hidden attribute, or role="presentation"/role="none", the hidden tracking and analytics frame class, where a name helps nobody. An expression value in any of these resolves unknowable → not flagged, consistent with the rest of the rule. These skips apply to the iframe arm only: a hidden unnamed button is still reported.
  • An iframe inside <svg>. SVG has no iframe, so the element never renders. (This is narrower than the <a> note above, which is judged in both namespaces because SVG <a> is a real link.)
  • A spread attribute on the iframe, whose rendered attributes are unknowable.

Why it matters

Assistive technology announces an interactive control by its accessible name. With none, a screen reader falls back to the bare role, “button” or “link”, indistinguishable from every other unnamed control on the page. A sighted user relying on an icon alone has no such gap, which is why the problem hides in visual review. An unnamed <iframe> has the same failure at a larger scale: it is announced as a bare frame, with no way to tell an embedded video from a map or an ad slot before entering it.

How to fix

Give the element visible text, a labelling attribute, or an alt on its icon image:

<button aria-label="Save">💾</button>
<a href="/x"><img src="i.png" alt="Home" /></a>
<input type="image" src="search.png" alt="Search" />
<iframe src="https://example.com/embed" title="Product demo video" loading="lazy"></iframe>

Overlap with the Svelte compiler

For iframes, the compiler warns as a11y_missing_attribute when title is missing. That overlap is deliberate: the compiler streams into the build log and does not score, gate, or suppress, while this rule feeds the health score, respects svelte-vitals-disable-next-line, and fails CI through --fail-on.

The compiler’s check is presence-only and title-only, which leaves four divergences:

  • aria-label/aria-labelledby name the frame for this rule, while the compiler still warns; only title silences it.
  • A blank title="" silences the compiler but computes no name, so this rule reports it. The blank title is a common way to quiet the build warning without naming anything.
  • A hidden or presentational iframe (aria-hidden="true", hidden, role="presentation"/"none") makes the compiler warn, while this rule skips it.
  • An expression-valued title silences both.

The button/link/image-button arms have no compiler counterpart (a11y_missing_content covers only anchors and headings, on different grounds).

Mode differences

None. This rule reads source, the same .svelte and .ts files, everywhere it runs. The CLI, the Vite plugin’s build pass, and the live dashboard’s static baseline all report it identically, and the rendered-HTML pass never re-evaluates it. Scoping a run with --route skips it: component-scoped rules have no route to attribute a finding to.

Disabling

If the name is supplied some other way this rule can’t see (e.g. a wrapping label element), silence a single element with <!-- svelte-vitals-disable-next-line a11y/accessible-name -->, or turn the rule off:

export default {
  rules: {
    'a11y/accessible-name': 'off'
  }
};