jsx-a11y/control-has-associated-label
Require buttons, links and other controls to have a text label
Also called jsx_a11y/control-has-associated-label
A button that only holds an icon is announced as "button". The person hearing it has to press it to find out what it does.
A control is an element people can operate, such as button, select, option, summary or a with an href, or an element with a role such as role="button".
These count as a label:
- Text or an expression inside the control.
aria-label,aria-labelledbyoralt, on the control or on an element inside it.- A component without children, such as
<Icon />, because it might render text. - Spread props, because the label might be in the spread.
Configure#
Off by default. To turn it on:
bunfig.toml
[lint.rules]
"jsx-a11y/control-has-associated-label" = "error" # or "warn"Every option, set to its default:
bunfig.toml
[lint.rules]
"jsx-a11y/control-has-associated-label" = ["error", {
depth = 2,
labelAttributes = [], # list of strings
controlComponents = [], # list of strings
ignoreElements = [
"audio",
"canvas",
"embed",
"input",
"textarea",
"tr",
"video",
],
ignoreRoles = [
"grid",
"listbox",
"menu",
"menubar",
"radiogroup",
"row",
"tablist",
"toolbar",
"tree",
"treegrid",
],
}]Examples#
example.jsx2 problems
const close = <button onClick={closeDialog}>A control must be associated with a text label.<img src="/x.svg" /></button>;
const next = <div role="button" tabIndex={0} onClick={goNext} />A control must be associated with a text label.;example.jsxNo problems
const close = <button onClick={closeDialog}><img src="/x.svg" alt="Close" /></button>;
const next = <div role="button" tabIndex={0} onClick={goNext} aria-label="Next page" />;
const save = <button onClick={saveDraft}>Save</button>;
const menu = <button onClick={openMenu}><MenuIcon /></button>;td and th count as controls too, so an empty table cell is reported:
example.jsx2 problems
const row = <tr><th />A control must be associated with a text label.<td />A control must be associated with a text label.</tr>;If your tables have empty cells, add td and th to ignoreElements, after the defaults:
example.jsxwith
{ "ignoreElements": ["audio", "canvas", "embed", "input", "textarea", "tr", "video", "td", "th"] }No problemsconst row = <tr><th /><td /></tr>;Options#
| Option | Default | What it does |
|---|---|---|
depth | 2 | How many levels of children to search for a label, up to 25. |
labelAttributes | [] | Other attributes that count as a label. |
controlComponents | [] | Components to check as controls. |
ignoreElements | ["audio", "canvas", "embed", "input", "textarea", "tr", "video"] | Elements to skip. Your list replaces the default. |
ignoreRoles | ["grid", "listbox", "menu", "menubar", "radiogroup", "row", "tablist", "toolbar", "tree", "treegrid"] | Roles to skip. Your list replaces the default. |
The text here is on the third level, so the default depth doesn't find it:
example.jsx1 problem
const save = <button>A control must be associated with a text label.<span><b>Save</b></span></button>;example.jsxwith
{ "depth": 3 }No problemsconst save = <button><span><b>Save</b></span></button>;example.jsxwith
{ "controlComponents": ["IconButton"] }1 problemconst remove = <IconButton icon="trash" />A control must be associated with a text label.;example.jsxwith
{ "controlComponents": ["IconButton"], "labelAttributes": ["tooltip"] }No problemsconst remove = <IconButton icon="trash" tooltip="Delete" />;