jsx-a11y/label-has-associated-control
Require every <label> to have text and a form control it belongs to
Also called jsx_a11y/label-has-associated-control
A screen reader announces a form field by reading its label. A <label> that isn't tied to a field is read as loose text, and clicking it doesn't focus anything. A <label> without text leaves the field without a name.
Tie a label to its control with htmlFor, or wrap the control in the label. By default either one is enough.
Configure#
Off by default. To turn it on:
[lint.rules]
"jsx-a11y/label-has-associated-control" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"jsx-a11y/label-has-associated-control" = ["error", {
assert = "either",
depth = 2,
labelComponents = [], # list of strings
labelAttributes = [], # list of strings
controlComponents = [], # list of strings
}]Examples#
const form = (
<form>
<label>A form label must be associated with a control.Email</label>
<input type="email" id="email" />
<label htmlFor="password" />A form label must have accessible text.
<input type="password" id="password" />
</form>
);const form = (
<form>
<label htmlFor="email">Email</label>
<input type="email" id="email" />
<label>
Password
<input type="password" />
</label>
</form>
);An expression such as {children} could be text or a control, so it counts as both:
const Field = ({ children }) => <label>{children}</label>;Options#
| Option | Default | What it does |
|---|---|---|
assert | "either" | How the label must be tied to its control: "htmlFor", "nesting", "both", or "either". |
depth | 2 | How many levels of children to search for the text and the control. At most 25. |
labelComponents | [] | Components to check as if they were <label>. |
labelAttributes | [] | Attributes that count as the label's text, in addition to alt, aria-label and aria-labelledby. |
controlComponents | [] | Components that count as a control, in addition to input, meter, output, progress, select and textarea. Globs such as "*Field" work. |
With "both", a label that only wraps its control is reported:
{ "assert": "both" }1 problemconst field = (
<label>A form label must be associated with a control.
Password
<input type="password" />
</label>
);The rule doesn't find a control that's deeper than depth. This is reported by default and allowed with depth: 3:
const field = (
<label>A form label must be associated with a control.
Country
<div><span><select /></span></div>
</label>
);{ "depth": 3 }No problemsconst field = (
<label>
Country
<div><span><select /></span></div>
</label>
);A component inside a label doesn't count as a control unless you list it:
const field = <label>A form label must be associated with a control.Email <TextField type="email" /></label>;{ "controlComponents": ["*Field"] }No problemsconst field = <label>Email <TextField type="email" /></label>;Your own label components are only checked if you list them:
{ "labelComponents": ["FieldLabel"] }1 problemconst field = <FieldLabel>A form label must be associated with a control.Email</FieldLabel>;An attribute you list in labelAttributes counts as the text:
{ "labelAttributes": ["text"] }No problemsconst field = <label htmlFor="email" text="Email" />;