-
Notifications
You must be signed in to change notification settings - Fork 4.1k
aria-required-children axe violation: loading and empty states render non-option children inside role="listbox" #6093
Description
Summary
Axe reports an aria-required-children violation when react-select renders its async loading/empty states. The violation originates inside react-select's internal markup, not consumer code.
Environment
- react-select version: latest (5.x)
- Axe rule:
aria-required-children - WCAG criterion: 1.3.1 Info and Relationships
Steps to reproduce
Scenario 1 — Loading state:
- Render a
<Select>(or<AsyncSelect>/<MultiSelect>) withisLoadingandmenuIsOpen - Run
axeagainst the rendered markup - Violation:
role="listbox"contains a status/loading message element without anyrole="option"children
Scenario 2 — No options / no results state:
- Render a
<Select>or multi-select variant withmenuIsOpenand an emptyoptionsarray, or with a search query that returns no matches - Run
axeagainst the rendered markup - Violation:
role="listbox"contains the "No options" / "No results found" message without anyrole="option"children
Root cause
The ARIA spec requires that role="listbox" must own at least one role="option" (or role="group") child. react-select renders its loading spinner and empty-state messages as plain <div> elements (no role="option") directly inside the role="listbox" container. Axe flags this as a required-children violation.
Workaround (currently applied)
BlockParty (First American's design system) suppresses this rule at the story level in affected components (MultiSelect, MultiSearchSelection):
// MultiSelect.stories.tsx — NoOptions story
NoOptions.parameters = {
a11y: {
config: {
rules: [{ id: 'aria-required-children', enabled: false }],
},
},
};
// MultiSearchSelection.stories.tsx — DisabledAndLoadingStates story
DisabledAndLoadingStates.parameters = {
a11y: {
config: {
rules: [{ id: 'aria-required-children', enabled: false }],
},
},
};
// MultiSearchSelection.stories.tsx — NoResults story
NoResults.parameters = {
a11y: {
config: {
rules: [
{
// react-select renders "No results found" inside role="listbox" without
// role="option" children when there are no matching results.
// Track upstream: https://github.com/JedWatson/react-select/issues/XXXX
id: 'aria-required-children',
enabled: false,
},
],
},
},
};Ask
Would react-select consider rendering the loading/empty-state messages using a role="option" with aria-disabled="true", or placing them in a separate role="status" live region outside the listbox? Either approach would satisfy the axe rule and provide a better screen reader experience.
If a fix or recommended pattern is available, we will remove the suppressions in BlockParty.