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>) with isLoading and menuIsOpen
- Run
axe against the rendered markup
- Violation:
role="listbox" contains a status/loading message element without any role="option" children
Scenario 2 — No options / no results state:
- Render a
<Select> or multi-select variant with menuIsOpen and an empty options array, or with a search query that returns no matches
- Run
axe against the rendered markup
- Violation:
role="listbox" contains the "No options" / "No results found" message without any role="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.
Summary
Axe reports an
aria-required-childrenviolation when react-select renders its async loading/empty states. The violation originates inside react-select's internal markup, not consumer code.Environment
aria-required-childrenSteps to reproduce
Scenario 1 — Loading state:
<Select>(or<AsyncSelect>/<MultiSelect>) withisLoadingandmenuIsOpenaxeagainst the rendered markuprole="listbox"contains a status/loading message element without anyrole="option"childrenScenario 2 — No options / no results state:
<Select>or multi-select variant withmenuIsOpenand an emptyoptionsarray, or with a search query that returns no matchesaxeagainst the rendered markuprole="listbox"contains the "No options" / "No results found" message without anyrole="option"childrenRoot cause
The ARIA spec requires that
role="listbox"must own at least onerole="option"(orrole="group") child. react-select renders its loading spinner and empty-state messages as plain<div>elements (norole="option") directly inside therole="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):Ask
Would react-select consider rendering the loading/empty-state messages using a
role="option"witharia-disabled="true", or placing them in a separaterole="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.