JedWatson / JedWatson/react-select
aria-required-children axe violation: loading and empty states render non-option children inside role="listbox"
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 28k
- Forks
- 4.1k
- PR merge metrics
- No merged PRs in 30d
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the loading and empty-state cases through the Select and AsyncSelect entry points with axe, then trace the internal listbox markup. Use the affected MultiSelect.stories.tsx and MultiSearchSelection.stories.tsx suppressions as acceptance references; done means the violations no longer require those suppressions and the messages remain usable to screen readers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100