# 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-labelledby` or `alt`, 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.

{/* Generated by src/codegen/lint-rule-docs.ts. Do not edit this section. */}

## Configure

Off by default. To turn it on:

```toml bunfig.toml
[lint.rules]
"jsx-a11y/control-has-associated-label" = "error" # or "warn"
```

Every option, set to its default:

```toml 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",
  ],
}]
```

{/* End of the generated section. */}

## Examples

Reported:

{/* bad {"count": 2} */}
```jsx
const close = <button onClick={closeDialog}><img src="/x.svg" /></button>;
const next = <div role="button" tabIndex={0} onClick={goNext} />;
```

Not reported:

{/* good */}
```jsx
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:

{/* bad {"count": 2} */}
```jsx
const row = <tr><th /><td /></tr>;
```

If your tables have empty cells, add `td` and `th` to `ignoreElements`, after the defaults:

{/* good {"options": [{"ignoreElements": ["audio", "canvas", "embed", "input", "textarea", "tr", "video", "td", "th"]}]} */}
```jsx
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:

{/* bad */}
```jsx
const save = <button><span><b>Save</b></span></button>;
```

{/* good {"options": [{"depth": 3}]} */}
```jsx
const save = <button><span><b>Save</b></span></button>;
```

{/* bad {"options": [{"controlComponents": ["IconButton"]}]} */}
```jsx
const remove = <IconButton icon="trash" />;
```

{/* good {"options": [{"controlComponents": ["IconButton"], "labelAttributes": ["tooltip"]}]} */}
```jsx
const remove = <IconButton icon="trash" tooltip="Delete" />;
```
