JedWatson / JedWatson/react-select

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

Open
#6,093 0 comments 0 reactions 0 assignees View on GitHub

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:

  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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.