Skip to content

Selector and MultiSelector put a role=separator Divider inside role=listbox (critical axe violation) #4994

Description

@cixzhang

What

role="listbox" only permits option and group children, but both selectors push a Divider — which renders role="separator" — straight into their listbox:

  • MultiSelector renders one above the options when hasSelectAll is set, and one per {type: 'divider'} / section option.
  • Selector does the same for {type: 'divider'} and section options. (Its "no results" branch already avoids this — it renders role="presentation" with a comment saying listbox only permits option/group children, so the constraint is known; the dividers just predate it.)

axe rates it critical (aria-required-children, WCAG 2 A / 1.3.1). A screen reader can drop or mis-report the listbox's children when an unallowed role appears among them.

Element has children which are not allowed: [role=separator]
<div id="…" role="listbox" aria-multiselectable="true" aria-labelledby="…">
  <div role="separator" aria-orientation="horizontal" class="astryx-divider subtle horizontal" …>

Why we haven't seen it

The a11y audit never opens a closed popup, and until #4993 no MultiSelector story used isDefaultOpen — so nothing in CI had ever looked inside an open listbox. The violation surfaced the moment a story opened one with hasSelectAll on. Anything that only exists inside an open popover is invisible to the audit today; worth keeping in mind beyond this bug.

To reproduce: any MultiSelector story with hasSelectAll + isDefaultOpen, then pnpm a11y:audit -- --components MultiSelector.

Fix direction

The dividers are decorative — they carry no information the options don't. Making them presentational inside a listbox (role="none", or a variant of Divider that renders no role) keeps the visual and removes the unallowed child. Sections are the case to think about: a titled section already renders a role="group", so its heading divider may want to move inside the group or become the group's aria-label source rather than sit beside it.

Both components share the pattern, so they should be fixed together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions