Skip to content

Commit aacbaad

Browse files
michaldudakclaude
andcommitted
[docs] Correct what goes inert while a popup animates out
The new handbook section left Popover out of the list of components that become inert and then placed it in the sentence describing the ones that do not, so it told readers the opposite of what ships. A hover-opened Popover runs a focus manager of its own now and hands focus back at close like every other session, which is exactly what makes inerting it safe. Also states two things the section implied but did not say: `inert` is not a style, so unlike the `pointer-events: none` it replaces a descendant cannot opt back in; and it is a backdrop you render yourself that keeps absorbing a click during the exit animation, not the internal one a modal popup adds, which goes inert with the rest. The Combobox page documented what happens to focus that was already in an external input, but not the case the default now covers: a control inside the popup that held focus when it closed hands focus back to the input. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 234bad7 commit aacbaad

3 files changed

Lines changed: 9 additions & 5 deletions

File tree

docs/src/app/(docs)/react/components/combobox/page.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,9 @@ The typed filter still resets once the popup closes.
250250

251251
`<Combobox.Input>` can be rendered inside `<Combobox.Popup>` to create a searchable select popup.
252252

253-
In this layout, focus returns to the trigger as soon as the popup closes, so the input can't be typed in or clicked while the popup plays its exit animation. Press <kbd>ArrowDown</kbd> or <kbd>ArrowUp</kbd> to reopen with a cleared filter. When the input sits outside the popup, focus stays in it, and typing during the exit animation reopens the popup and filters.
253+
In this layout, focus returns to the trigger as soon as the popup closes, so the input can't be typed in or clicked while the popup plays its exit animation. Press <kbd>ArrowDown</kbd> or <kbd>ArrowUp</kbd> to reopen with a cleared filter.
254+
255+
When the input sits outside the popup, focus stays in it, and typing during the exit animation reopens the popup and filters. A control inside the popup that held focus when it closed hands focus back to the input, unless the popup was dismissed by a press outside it — that press is the user moving focus themselves. Passing `finalFocus` replaces this behavior.
254256

255257
import { DemoComboboxInputInsidePopup } from './demos/input-inside-popup';
256258

docs/src/app/(docs)/react/handbook/animation/page.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ Base UI components can be animated using CSS transitions, CSS animations, or Ja
99

1010
A component that stays in the DOM while closed is already closed. Where a component has an explicit close-time focus handoff, Base UI marks the closed subtree with the [`inert`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert) attribute. The closing popup is then neither exposed to assistive technology nor reachable by the Tab key. This applies whenever the popup is closed and still rendered, including a `keepMounted` popup that has never been opened.
1111

12-
This applies to [Dialog](/react/components/dialog), [Alert Dialog](/react/components/alert-dialog), [Drawer](/react/components/drawer), [Menu](/react/components/menu), [Context Menu](/react/components/context-menu), [Menubar](/react/components/menubar), [Select](/react/components/select), [Combobox](/react/components/combobox), and [Autocomplete](/react/components/autocomplete). [Popover](/react/components/popover) can open by hover without its focus manager, while [Tooltip](/react/components/tooltip), [Preview Card](/react/components/preview-card), and [Navigation Menu](/react/components/navigation-menu) do not hand focus off at this boundary. Their closing positioners block pointer events but do not become inert.
12+
This applies to [Dialog](/react/components/dialog), [Alert Dialog](/react/components/alert-dialog), [Drawer](/react/components/drawer), [Popover](/react/components/popover), [Menu](/react/components/menu), [Context Menu](/react/components/context-menu), [Menubar](/react/components/menubar), [Select](/react/components/select), [Combobox](/react/components/combobox), and [Autocomplete](/react/components/autocomplete) — a Popover opened by hover included, since it runs a focus manager of its own and hands focus back like any other session. [Tooltip](/react/components/tooltip), [Preview Card](/react/components/preview-card), and [Navigation Menu](/react/components/navigation-menu) have no such handoff, so their closing positioners block pointer events but never become inert.
1313

1414
For components that become inert, focus is handed back when the component closes rather than when the exit animation finishes. A popup closed while focus is inside it moves focus to its trigger, or to whatever was focused before it opened. Passing `finalFocus` opts out, whether it is `false` or a function returning `false`: focus is then yours to move, and if you do not move it, it drops to `<body>` for the rest of the exit animation.
1515

16-
Closing positioners stop receiving pointer events, so a click during the exit animation passes through to whatever is behind the popup rather than doing nothing. A backdrop is not inert, so a modal Dialog or Drawer still absorbs that click. Keep exit animations short if a stray click in that window would be disruptive.
16+
Closing positioners stop receiving pointer events, so a click during the exit animation passes through to whatever is behind the popup rather than doing nothing. A backdrop you render yourself is not inert and still absorbs that click, while the internal backdrop a modal popup adds for itself does not. `inert` is not a style, so unlike the `pointer-events: none` it replaces, a descendant cannot opt back in: there is no way to keep part of a closing popup interactive. Keep exit animations short if a stray click in that window would be disruptive.
1717

1818
## CSS transitions
1919

packages/react/src/popover/popup/PopoverPopup.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,10 +598,12 @@ describe('<Popover.Popup />', () => {
598598
const inside = await screen.findByTestId('inside');
599599
await waitFor(() => expect(inside).toHaveFocus());
600600

601-
// Escape leaves focus inside the closing popup, so the click below is the user's own
602-
// deliberate focus move rather than the popup handing focus off.
601+
// The handoff runs at close, so Escape moves focus to `finalFocus` straight away. That
602+
// makes the click below the user's own deliberate move on top of it, rather than something
603+
// the popup is still about to undo.
603604
await user.keyboard('{Escape}');
604605
expect(screen.getByTestId('popup')).toHaveAttribute('data-ending-style');
606+
await waitFor(() => expect(screen.getByTestId('final-focus')).toHaveFocus());
605607

606608
const elsewhere = screen.getByTestId('elsewhere');
607609
await user.click(elsewhere);

0 commit comments

Comments
 (0)