Skip to content

Select menus need a label

  • Rule: axe/select-name
  • Checks: Select element must have an accessible name
  • WCAG: 4.1.2
  • Fix class: content: A fix needs authored text.
  • People who use a screen reader or a braille display, most of them blind.
  • People who control the page by speaking.

The first option, such as “Choose a country”, stands in for the label

Section titled “The first option, such as “Choose a country”, stands in for the label”

Add a visible <label> connected with for and id. The first option is a value, not a name, and it disappears from view once people choose.

<label for="country">Country</label>
<select id="country" autocomplete="country">
<option value="">Choose a country</option>
<option value="CA">Canada</option>
<option value="MX">Mexico</option>
</select>

Visible text names the select but is not connected to it

Section titled “Visible text names the select but is not connected to it”

Make the text a <label> whose for matches the select’s id, and keep that id unique on the page.

Several selects share one visible label, such as a date split into month and year

Section titled “Several selects share one visible label, such as a date split into month and year”

Group them in a <fieldset> whose <legend> holds the shared label, and give each select its own <label> for its part.

<fieldset>
<legend>Card expiration date</legend>
<label for="exp-month">Month</label>
<select id="exp-month" autocomplete="cc-exp-month">
<option>01</option>
<option>02</option>
</select>
<label for="exp-year">Year</label>
<select id="exp-year" autocomplete="cc-exp-year">
<option>2026</option>
<option>2027</option>
</select>
</fieldset>

Naming the select only with aria-label where a visible label would fit

Section titled “Naming the select only with aria-label where a visible label would fit”

Screen readers announce it, but sighted people see no label once a value is chosen, and voice control users cannot tell what to say.

axe accepts it, but it appears only on mouse hover, so most people never see it. axe’s best-practice rule label-title-only reports it.

The part of the fix that differs in React, in its syntax. The rest of the guidance holds.

A select component hard-codes its id or renders its label as plain text

Section titled “A select component hard-codes its id or renders its label as plain text”

Render a <label> whose htmlFor is the caller’s id or one from useId, and give the <select> the same id, set after spreading the other props.

function SelectField({ label, options, id: idProp, ...rest }) {
const generatedId = useId();
const id = idProp ?? generatedId;
return (
<>
<label htmlFor={id}>{label}</label>
<select {...rest} id={id}>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</>
);
}

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.