diff --git a/src/cdk/overlay/overlay.md b/src/cdk/overlay/overlay.md
index 8030aaa6b856..6b5908bcddfc 100644
--- a/src/cdk/overlay/overlay.md
+++ b/src/cdk/overlay/overlay.md
@@ -1,4 +1,4 @@
-The `overlay` package provides a way to open floating panels on the screen.
+The `overlay` package provides a way to open floating panels on the screen.
### Initial setup
The CDK overlays depend on a small set of structural styles to work correctly. If you're using
@@ -87,6 +87,46 @@ strategy will typically inject `ScrollDispatcher` (from `@angular/cdk/scrolling`
of when scrolling takes place. See the documentation for `ScrollDispatcher` for more information
on how scroll events are detected and dispatched.
+#### Using the native Popover API
+As of Angular v21, the CDK overlay supports rendering overlays as native popover elements instead
+of using the traditional overlay container. This uses the browser's native [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API),
+which provides improved accessibility, automatic focus management, and better handling of scrolling
+and positioning.
+
+To enable the popover behavior, set the `usePopover` option to `true` when creating an overlay:
+
+```ts
+const overlayRef = overlay.create({
+ positionStrategy: this.overlay.position()
+ .flexibleConnectedTo(trigger)
+ .withPositions(positions),
+ usePopover: true,
+});
+```
+
+When using `FlexibleConnectedPositionStrategy` with popovers, you can also specify the popover
+insertion point:
+
+```ts
+const overlayRef = overlay.create({
+ positionStrategy: this.overlay.position()
+ .flexibleConnectedTo(trigger)
+ .withPositions(positions)
+ .withPopoverInsertionPoint(triggerElement),
+ usePopover: 'auto', // Can also use 'auto', 'manual'
+});
+```
+
+**Benefits of using the native Popover API:**
+- Better accessibility with automatic focus management
+- Automatic dismissal on outside click (for auto popovers)
+- Improved performance with less CSS in the critical path
+- Native browser support for popover stacking
+- Better integration with screen readers
+
+**Note:** The popover API is not supported on older browsers. Always provide a fallback or test
+browser compatibility before using this feature in production.
+
### The overlay container
The `OverlayContainer` provides a handle to the container element in which all individual overlay
elements are rendered. By default, the overlay container is appended directly to the document body
diff --git a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html
index bce80529017e..4e66c0cecb30 100644
--- a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html
+++ b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html
@@ -2,6 +2,7 @@
@@ -100,43 +109,32 @@ export class SimpleToolbarRadioButton {
}
-
-
+
+
`,
})
export class SimpleCombobox {
dir = inject(Directionality).valueSignal;
- popover = viewChild('popover');
listbox = viewChild>(Listbox);
combobox = viewChild>(Combobox);
+ panelWidth = signal(undefined);
+
value = signal('Normal text');
options = ['Normal text', 'Title', 'Subtitle', 'Heading 1', 'Heading 2', 'Heading 3'];
constructor() {
afterRenderEffect(() => {
- const popover = this.popover()!;
const combobox = this.combobox()!;
- combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover();
+ if (combobox.expanded()) {
+ const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
+ this.panelWidth(comboboxRect?.width);
+ } else {
+ this.panelWidth(undefined);
+ }
this.listbox()?.scrollActiveItemIntoView();
});
}
-
- showPopover() {
- const popover = this.popover()!;
- const combobox = this.combobox()!;
-
- const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
- const popoverEl = popover.nativeElement;
-
- if (comboboxRect) {
- popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
- popoverEl.style.left = `${comboboxRect.left - 1}px`;
- }
-
- popover.nativeElement.showPopover();
- }
}
diff --git a/src/material/autocomplete/autocomplete.md b/src/material/autocomplete/autocomplete.md
index 12306f3f9b7d..361268b09104 100644
--- a/src/material/autocomplete/autocomplete.md
+++ b/src/material/autocomplete/autocomplete.md
@@ -148,6 +148,55 @@ attribute, or the `aria-labelledby` attribute.
`MatAutocomplete` preserves focus on the text trigger, using `aria-activedescendant` to support
navigation though the autocomplete options.
+### Making combobox/autocomplete overlays consistent with menus
+
+Some implementations render the combobox panel into the DOM always and hide it with CSS (for
+example by setting `visibility: hidden` or `opacity: 0`). This can cause differences in behavior
+compared to components (like menus) that attach the overlay only when the panel is opened. The
+differences can affect keyboard event targeting, focus handling, and performance when many
+comboboxes are present.
+
+Recommended approach:
+- Render the overlay only when the panel is open (attach/detach) or use the CDK "popover"
+ insertion strategy which places the panel inline with the origin when appropriate. This keeps
+ the menu and combobox behavior consistent and avoids relying on CSS to toggle visibility.
+
+How to opt-in
+- For template-driven overlays (via `cdkConnectedOverlay`) set the `cdkConnectedOverlayUsePopover`
+ input to `'inline'` or a `FlexibleOverlayPopoverLocation` value so that the overlay will be
+ inserted as a popover when supported:
+
+```html
+
+
+
+```
+
+- For components that create an `OverlayRef` programmatically (like `MatAutocompleteTrigger`),
+ ensure the position strategy uses `withPopoverLocation('inline')` when appropriate. The
+ autocomplete implementation in this repo already uses `withPopoverLocation('inline')` in
+ `_getOverlayPosition()`; if you have custom combobox code, apply the same strategy:
+
+```ts
+const strategy = createFlexibleConnectedPositionStrategy(injector, origin)
+ .withFlexibleDimensions(false)
+ .withPush(false)
+ .withPopoverLocation('inline');
+
+const overlayRef = createOverlayRef(injector, {positionStrategy: strategy, width});
+```
+
+Notes
+- After switching to inline/popover insertion, verify keyboard navigation and focus behavior in
+ complex interactions (e.g., nested modals or form fields). If you rely on `visibility: hidden`
+ to keep the panel in the DOM for styling reasons, consider moving that styling into the
+ panel's classes and controlling rendering via the overlay open state instead.
+
+
By default, `MatAutocomplete` displays a checkmark to identify the selected item. While you can hide
the checkmark indicator via `hideSingleSelectionIndicator`, this makes the component less accessible
by making it harder or impossible for users to visually identify selected items.