From 8414de8e1a820c434e74552dae7ca2d95cee85fe Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Tue, 5 May 2026 07:08:56 -0400 Subject: [PATCH 1/3] Disable feature flag if `portalContainerName` is true --- packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index aa5c0818d0a..a89234d59da 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -175,7 +175,8 @@ export const AnchoredOverlay: React.FC typeof document !== 'undefined' && 'anchorName' in document.documentElement.style, ) - const cssAnchorPositioning = cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning + const cssAnchorPositioning = + cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning && !overlayProps?.portalContainerName // Only use Popover API when both CSS anchor positioning is enabled AND renderAs is true const shouldRenderAsPopover = cssAnchorPositioning && renderAs === 'popover' const anchorRef = useProvidedRefOrCreate(externalAnchorRef) From 7d0ed5462583e3635a0391d2f54bb8c6c9418671 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Tue, 5 May 2026 08:16:10 -0400 Subject: [PATCH 2/3] Add changeset --- .changeset/evil-coins-tease.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/evil-coins-tease.md diff --git a/.changeset/evil-coins-tease.md b/.changeset/evil-coins-tease.md new file mode 100644 index 00000000000..f64fb54c3ab --- /dev/null +++ b/.changeset/evil-coins-tease.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +AnchoredOverlay: Disables CSS anchor positioning if `portalContainerName` is true. (behind `primer_react_css_anchor_positioning` feature flag) From 301953d3f8a626bb35f74ec7a2f5d2c3f814c187 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Tue, 5 May 2026 10:46:08 -0400 Subject: [PATCH 3/3] Add test --- .../AnchoredOverlay/AnchoredOverlay.test.tsx | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx index 4caecfa0682..405970b1938 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx @@ -8,6 +8,7 @@ import BaseStyles from '../BaseStyles' import type {AnchorPosition} from '@primer/behaviors' import {implementsClassName} from '../utils/testing' import {FeatureFlags} from '../FeatureFlags' +import {registerPortalRoot} from '../Portal' import overlayClasses from '../Overlay/Overlay.module.css' import anchoredOverlayClasses from './AnchoredOverlay.module.css' @@ -174,9 +175,7 @@ describe.each([true, false])( expect(mockCloseCallback).toHaveBeenCalledWith('escape') }) - // onPositionChange is not supported when the CSS anchor positioning flag is enabled, - // because positioning is handled by the browser rather than `useAnchoredPosition`. - it.skipIf(withCSSAnchorPositioningFeatureFlag)('should call onPositionChange when provided', async () => { + it('should call onPositionChange when provided', async () => { const mockPositionChangeCallback = vi.fn(({position}: {position: AnchorPosition}) => position) render( { const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).not.toHaveAttribute('popover') }) + + describe('when overlayProps.portalContainerName is provided', () => { + it('should fall back to JS positioning (data-anchor-position="false") even with the flag enabled', () => { + const portalRoot = document.createElement('div') + document.body.appendChild(portalRoot) + registerPortalRoot(portalRoot, 'anchoredOverlayTestPortal') + + const {baseElement} = render( + + + {}} + onClose={() => {}} + renderAnchor={props => } + overlayProps={{portalContainerName: 'anchoredOverlayTestPortal'}} + > + + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).toHaveAttribute('data-anchor-position', 'false') + expect(overlay).not.toHaveClass(anchoredOverlayClasses.AnchoredOverlay) + + portalRoot.remove() + }) + + it('should not opt into the Popover API even when renderAs="popover"', () => { + const portalRoot = document.createElement('div') + document.body.appendChild(portalRoot) + registerPortalRoot(portalRoot, 'anchoredOverlayTestPortalPopover') + + const {baseElement} = render( + + + {}} + onClose={() => {}} + renderAnchor={props => } + renderAs="popover" + overlayProps={{portalContainerName: 'anchoredOverlayTestPortalPopover'}} + > + + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).not.toHaveAttribute('popover') + + const anchor = baseElement.querySelector('[aria-haspopup="true"]') + expect(anchor).not.toHaveAttribute('popovertarget') + + portalRoot.remove() + }) + }) }) describe('with primer_react_css_anchor_positioning feature flag disabled', () => {