aria-required-children axe violation: loading and empty states render non-option children inside role="listbox"

Author: endpoint-vtsangCreated Mar 4, 2026Updated Mar 4, 2026

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:

  1. Render a <Select> (or <AsyncSelect> / <MultiSelect>) with isLoading and menuIsOpen
  2. Run axe against the rendered markup
  3. Violation: role="listbox" contains a status/loading message element without any role="option" children

Scenario 2 — No options / no results state:

  1. Render a <Select> or multi-select variant with menuIsOpen and an empty options array, or with a search query that returns no matches
  2. Run axe against the rendered markup
  3. 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):

typescript
// 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.