Skip to content

Collection components in the tab row throw; trigger guards can't cover triggerless popovers #10368

Description

@RobHannay

Provide a general summary of the issue here

Collection components rendered alongside TabList inside Tabs throw, because they render during the Tabs collection's hidden pass and contribute items to the Tabs document instead of building their own.

#10019 addressed this by making MenuTrigger, Select and ComboBox hideable, and #10367 adds DialogTrigger. Two shapes remain that a per-trigger guard doesn't reach — and the second can't be reached by that strategy at all, because there is no trigger component to guard.

🤔 Expected Behavior?

Collection components in the tab row build their own collection and render normally.

😯 Current Behavior

1. A TagGroup in the tab row (e.g. chips for currently-applied filters, beside the tabs):

TypeError: Cannot destructure property 'onAction' of '_utils.listMap.get(...)' as it is undefined.

2. A standalone controlled Popover (triggerRef + isOpen, no trigger component) containing a Menu:

TypeError: Cannot read properties of null (reading 'isDisabled')   // useMenuItem.ts:173

The second is the interesting one: the hideable-trigger approach can't cover it, since the popover is positioned via triggerRef rather than wrapped in a MenuTrigger/DialogTrigger.

Reduced further, a bare Menu, ListBox or GridList as a sibling of TabList throws for the same reason (three different internal errors). Those are minimal reductions rather than realistic usage — the two above are the ones I would actually expect people to write.

💁 Possible Solution

No PR, since this is a design call rather than a patch.

The per-trigger guards stop the gateway rendering. The underlying rule — a collection component should not contribute items to an unrelated ancestor's collection document — isn't enforced anywhere, so each new shape needs its own guard, and shapes without a trigger have nowhere to put one.

CollectionBuilder is where the rule could live:

// If a document was provided above us, we're already in a hidden tree. Just render the content.
let doc = useContext(CollectionDocumentContext);
if (doc) {
  return props.content as ReactElement;
}

Correct for a nested collection that genuinely belongs to the ancestor (submenu items joining the parent menu, Select's ListBox); wrong for an independent collection root that merely happens to be nested. Distinguishing those — perhaps opt-in from the components that need inheritance — would cover all of these at once.

🔦 Context

Found while fixing DialogTrigger (#10367) for a tab row containing a filter popover. That PR fixes our case; this issue is only about the shapes it doesn't reach.

💻 Code Sample

function inTabs(node) {
  return (
    <Tabs>
      <div>
        <TabList>
          <Tab id="key1">First Tab</Tab>
          <Tab id="key2">Second Tab</Tab>
        </TabList>
        {node}
      </div>
      <TabPanel id="key1">A</TabPanel>
      <TabPanel id="key2">B</TabPanel>
    </Tabs>
  );
}

// 1. TagGroup in the tab row
render(inTabs(
  <TagGroup aria-label="Active filters">
    <TagList><Tag id="a">Filter A</Tag></TagList>
  </TagGroup>
));

// 2. standalone controlled Popover holding a Menu
function StandalonePopover() {
  let ref = useRef(null);
  return (
    <>
      <Button ref={ref}>Open</Button>
      <Popover triggerRef={ref} isOpen={false} aria-label="Filters">
        <Menu aria-label="Filter options"><MenuItem>Item 1</MenuItem></Menu>
      </Popover>
    </>
  );
}
render(inTabs(<StandalonePopover />));

Control: the identical Menu element renders fine with no Tabs wrapper, and nested inside plain divs — the Tabs wrapper is the only variable.

🌍 Your Environment

Software Version(s)
react-aria-components 1.19.0 / main (with #10367 applied)
Browser n/a (reproduces in jsdom)
Operating System macOS

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