Linter · Rules
jsx-a11y/control-has-associated-label
Require buttons, links and other controls to have a text 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
Reported:
const close = <button onClick={closeDialog}><img src="/x.svg" /></button>;
const next = <div role="button" tabIndex={0} onClick={goNext} />;
Not reported:
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:
const row = <tr><th /><td /></tr>;
If your tables have empty cells, add td and th to ignoreElements, after the defaults:
const 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:
const save = <button><span><b>Save</b></span></button>;
const save = <button><span><b>Save</b></span></button>;
const remove = <IconButton icon="trash" />;
const remove = <IconButton icon="trash" tooltip="Delete" />;