Skip to content

feat: Make ComboBox optionally modal#10314

Open
omarnyte wants to merge 4 commits into
adobe:mainfrom
omarnyte:4937-make-combobox-optionally-modal
Open

feat: Make ComboBox optionally modal#10314
omarnyte wants to merge 4 commits into
adobe:mainfrom
omarnyte:4937-make-combobox-optionally-modal

Conversation

@omarnyte

@omarnyte omarnyte commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Closes #4937

This MR optionally makes ComboBox modal by exposing an isNonModal attribute on both useComboBox and RAC's ComboBox. It does not implement that behavior on Spectrum's ComboBox.

Existing behavior (i.e. hiding outside elements with aria-hidden while the popover is open) is preserved as the default to respect the portal strategy outlined here. When isNonModal={true}, outside elements receive inert to prevent interactions whenever the popover is open.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

useComboBox

Modal (new behavior)

  1. Visit Modal story
  2. Open popopver
  3. Ensure that p tags above and below the ComboBox have inert attribute
  4. Press either of the links. Ensure that
    a. The popover closes
    b. The browser does not navigate to the link
    c. The p tags no longer have the inert attribute

Non-Modal (existing behavior)

  1. Visit NonModal story
  2. Open popopver
  3. Ensure that p tags above and below the ComboBox have aria-hidden attribute
  4. Press either of the links. Ensure that
    a. The popover closes
    b. The browser navigates to the link

ComboBox (React Aria Components)

Modal (new behavior)

  1. Visit ModalComboBox story
  2. Open popopver
  3. Ensure that p tags above and below the ComboBox have inert attribute
  4. Press either of the links. Ensure that
    a. The popover closes
    b. The browser does not navigate to the link
    c. The p tags no longer have the inert attribute

Non-Modal (existing behavior)

  1. Visit NonModalComboBox story
  2. Open popopver
  3. Ensure that p tags above and below the ComboBox have aria-hidden attribute
  4. Press either of the links. Ensure that
    a. The popover closes
    b. The browser navigates to the link

🧢 Your Project:

Screenshots

Modal (new behavior)

Screenshot 2026-07-08 at 10 30 52 AM

Non-Modal (existing behavior)

Screenshot 2026-07-08 at 10 31 07 AM

*
* @default true
*/
isNonModal?: boolean;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name isModal is more clear, but I used isNonModal to align with Popover's isNonModal prop.

isReadOnly,
isDisabled
isDisabled,
isNonModal = true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true preserves existing behavior.

import {useOverlay} from '../../src/overlays/useOverlay';

export function ComboBox(props: AriaComboBoxProps<any>): JSX.Element {
export function ComboBox(props: AriaComboBoxProps<any> & {isNonModal?: boolean}): JSX.Element {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't expose the modal behavior on Spectrum's ComboBox, so we need this type intersection here.

@snowystinger

Copy link
Copy Markdown
Member

Hey, thanks for the PR. So we intentionally use aria-hidden instead of inert here so that users can tab out of a combobox to the next element (in a form for example) regardless of if the popover is open. I feel like this is a bit of a false positive from axe.

@omarnyte

omarnyte commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@snowystinger thanks for the context!

Are you open to a change that applies a data-a11y-ignore="aria-hidden-focus" attribute to those elements that get aria-hidden when the ComboBox popover is open? That would allow us to ignore false positives without turning off the rule entirely.

@snowystinger

snowystinger commented Jul 9, 2026

Copy link
Copy Markdown
Member

a change that applies a data-a11y-ignore="aria-hidden-focus"

I'm not sure what this would look like. Either it involves a lot of re-renders or it's a lot of searching the dom for inputs and anything else that could trigger it and then the cleanup.
Unfortunately, applying aria-hidden is already a pretty expensive (takes a long time, has caused noticeable delays in the past) bit of code to run. We'd be reluctant to increase how long it takes to run just for an AXE false positive.

Would it be possible to limit what you run AXE on? if you have a combobox open, you just need to run it on the input and the popover, nothing else actually matters

@omarnyte

Copy link
Copy Markdown
Contributor Author

The logic to add/remove the data-a11y-ignore attribute can ride the existing logic to add/remove the aria-hidden attribute in the ariaHideOutside function. It would be a two-line change to apply that attribute in all cases. It would be a few more lines to apply it conditionally.

image

I didn't notice any significant performance difference between applying just aria-hidden and applying both aria-hidden and data-a11y-ignore in the Storybook story. The profiling logs are below (opening the popover; selecting the first item; closing the popover), but I do realize that a Storybook story is tiny compared to a full app.

profiling-dataAfter.json
profiling-data.Before.json

Would it be possible to limit what you run AXE on?

We can choose to turn off a rule entirely, or we can configure a CSS selector of elements to consider/ignore. We prefer not turning off rules unless absolutely necessary. We might be able to use a CSS selector that ignores any elements with aria-hidden if a ComboBox popover is open somewhere on the page. But I haven't looked closely enough at where that popover gets portalled to determine if it's feasible.

@snowystinger

snowystinger commented Jul 10, 2026

Copy link
Copy Markdown
Member

would data-a11y-disabled be applied to the element that has aria-hidden? or does it need to be on the actual child/descendent input's and tabIndex=-1's?

Would it be ok to only do it during dev, and not in production builds?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make it possible to disable ariaHideOutside with ComboBox

2 participants