[combobox] Prevent inaccessible popup state during exit animations
- Dominant language
- TypeScript
- Stars
- 10.9k
- Forks
- 543
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 101
Description
# Bug report
## Current behavior
When a Combobox popup has an exit animation using `data-ending-style`, closing it creates a temporary mismatch between its logical and visual lifecycles:
- `open` becomes `false`, so the combobox reports `aria-expanded="false"` and removes `aria-controls`.
- `mounted` stays `true` until the animation finishes, so the popup, listbox, and focus guards remain in the DOM.
- The focus guards remain `aria-hidden="true"` and `tabindex="0"`. After the popup closes, they can receive and retain focus because there is no longer a valid target inside the popup.
During this interval, axe can report both `aria-input-field-name` for the listbox after it loses its combobox context and `aria-hidden-focus` for the guards.
The DOM state is deterministic for the duration of the exit animation. An automated accessibility scan only catches it intermittently because the scan may run before or after the animation completes.
## Expected behavior
Once the Combobox is logically closed, nodes kept only for the visual exit animation should not remain exposed to assistive technology or sequential focus navigation.
Alternatively, the popup's ARIA relationship and focus behavior should remain coherent until the animated subtree is removed.
## Reproducible example
```tsx
Choose a fruit
{(item: string) => (
{item}
)}
```
```css
@keyframes fade-out {
to {
opacity: 0;
}
}
.Popup[data-ending-style] {
animation: fade-out 5s linear;
}
```
1. Open the Combobox.
2. Select an option.
3. Before the 5-second exit animation finishes, inspect the DOM or run axe.
4. The popup and listbox are still mounted, the combobox ARIA relationship has already been removed, and the inside focus guards are still focusable.
## Base UI version
Latest `master` at `582d51a8383b2b86b9bf466ba2ff7708807c1639`.
## Which browser are you using?
Chrome / Chromium.
## Which OS are you using?
macOS. The same state was also observed in a Linux Chromium CI environment.
## Which assistive tech are you using (if applicable)?
axe-core 4.12.1, plus direct DOM and focus inspection.
## Additional context
The mismatch comes from these ownership boundaries:
- [Combobox ARIA state follows `open`](https://github.com/mui/base-ui/blob/582d51a8383b2b86b9bf466ba2ff7708807c1639/packages/react/src/combobox/root/AriaCombobox.tsx#L1262-L1288).
- [The transition hook keeps `mounted` true while ending](https://github.com/mui/base-ui/blob/582d51a8383b2b86b9bf466ba2ff7708807c1639/packages/react/src/internals/useTransitionStatus.ts#L21-L33).
- [Combobox keeps `FloatingFocusManager` enabled while mounted](https://github.com/mui/base-ui/blob/582d51a8383b2b86b9bf466ba2ff7708807c1639/packages/react/src/combobox/popup/ComboboxPopup.tsx#L127-L146).
- [Focus guards are rendered with `tabindex="0"`](https://github.com/mui/base-ui/blob/582d51a8383b2b86b9bf466ba2ff7708807c1639/packages/react/src/utils/FocusGuard.tsx#L26-L40).
Relevant guidance:
- [WAI-ARIA Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)
- [ACT rule: Element with aria-hidden has no content in sequential focus navigation](https://www.w3.org/WAI/standards-guidelines/act/rules/6cfa84/proposed/)
Contributor guide
Research direction
Start with packages/react/src/combobox/root/AriaCombobox.tsx, packages/react/src/internals/useTransitionStatus.ts, packages/react/src/combobox/popup/ComboboxPopup.tsx, and packages/react/src/utils/FocusGuard.tsx. Reproduce the provided 5-second exit animation and inspect focus and axe results while it runs; done means the logically closed popup no longer exposes inaccessible content or focusable guards, or its ARIA relationship remains coherent until removal.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100