squirrelscan
GitHub

Command roles: buttons, links and menu items need names

Anything that performs an action has to announce what it does. squirrel lists every command element on a page with no accessible name.

What a command element is

ARIA groups the roles that perform a single action as commands: button, link, menuitem, menuitemcheckbox and menuitemradio. Native <button> and <a href> carry the first two implicitly.

A command with no accessible name is announced as bare role, so the user hears “menu item” with no idea what it does. Visible text is usually enough; icon-only commands need aria-label, aria-labelledby, a nested <img> with alt, or an <svg><title>.

What squirrel checks

One check, aria-command-name, at severity error. It covers three sets: every element with one of the five command roles, every <button> with no explicit role, and every <a href> with no explicit role.

  • Fail when such an element has none of the following: a non-empty aria-label; an aria-labelledby pointing at an element with text; a non-empty title; text content; a nested <img> with non-empty alt; a nested <svg><title> with text. Report message: N command element(s) without accessible names. Items are listed as div[role="menuitem"], button[type="submit"] or a[href="/checkout"], truncating a long href at 30 characters, with up to ten shown and a count of the rest.
  • aria-command-name-unconfirmed warns with N command element(s) without accessible names may be hidden by a style (unconfirmed) for unnamed elements a style may hide in a way squirrel cannot settle.
  • Pass with All command elements have accessible names, or All command elements in the accessibility tree have accessible names with hiddenWithoutName when unnamed elements were skipped as hidden.

A command element out of the accessibility tree has nothing to announce, so it is not reported: one with the hidden attribute (except hidden="until-found"), aria-hidden="true", display: none on itself or an ancestor or visibility: hidden on itself or an ancestor, set inline or by a <style> rule squirrel can decide (such as .closed { display: none }), or inside a closed <dialog> or an unopened popover. It still needs a name before it can be shown. Hiding squirrel cannot settle is reported as unconfirmed instead: an inline style it cannot read exactly, a <style> rule it cannot decide, and, on a page that also loads an external stylesheet, hiding from a <style> rule or visibility inherited from an ancestor. External stylesheets are not read, so a command element hidden only by the site’s external CSS, such as a closed modal, is still reported. See What squirrel can see for the details.

A page past squirrel’s style limits (more than 5,000 style rules, a <style> block over the size limit, a very long selector, a no-break or other non-ASCII space in a style block, or a rule nested inside another) is read as unconfirmed for every element a style might hide: the rules past the limit could hide or show any of them, so an unnamed control is reported as unconfirmed, not as a failure. CSS nesting is not read: a nested rule that sets display or visibility (b { @media (min-width: 0) { display: none } }, .a { & .b { display: none } }) leaves the elements it could reach unconfirmed, and a nested rule that sets neither changes nothing.

A control that carries a no-js class, itself or on an ancestor other than <html>, is reported as unconfirmed on a page with an external stylesheet or an @import, with the reason that it may be hidden when JavaScript runs. A theme usually hides that fallback with a rule such as .js .no-js { display: none } in a stylesheet squirrel does not read. The same control on a page whose styles were all read keeps its verdict, and a class such as no-js-hidden is not a marker.

This rule overlaps a11y/button-name and a11y/link-text on purpose. It is the one that also reaches menu items built from <div> and <span>.

How to fix it

<div role="menuitem" aria-label="Duplicate row">
  <svg aria-hidden="true">...</svg>
</div>

Put the name on the element that carries the role, not on a child, and hide any decorative icon inside it with aria-hidden="true".

A link wrapping only a background-image logo is the usual false positive. There is no markup for squirrel to read, and there is nothing for a screen reader to read either, so the fix is the same: add aria-label.

Rule IDa11y/aria-command-name
CategoryAccessibility
ScopePer-page
Severityerror
Weight7/10

Enable / disable

Disable this rule

[rules]
disable = ["a11y/aria-command-name"]

Disable all Accessibility rules

[rules]
disable = ["a11y/*"]

Enable only this rule

[rules]
enable = ["a11y/aria-command-name"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Accessibility findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Accessibility section of the report. Every unnamed command is listed with a selector you can search for. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close