Skip to content

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

@endpoint-vtsang

Description

@endpoint-vtsang

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):

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions