From c0d75b603afd70eeece322439177d4627ce862df Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Fri, 17 Apr 2026 10:54:30 -0400 Subject: [PATCH 1/9] Make popover in `AnchoredOverlay` opt-in --- .../src/AnchoredOverlay/AnchoredOverlay.tsx | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 7421e6a84d6..d7b29f816e5 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -120,6 +120,13 @@ interface AnchoredOverlayBaseProps extends Pick + /** + * When enabled (and CSS anchor positioning feature flag is on), uses the Popover API + * to render the overlay in the browser's top layer. This helps the overlay escape + * stacking contexts and appear above other elements like sticky headers. + * @default false + */ + usePopoverApi?: boolean } export type AnchoredOverlayProps = AnchoredOverlayBaseProps & @@ -162,10 +169,13 @@ export const AnchoredOverlay: React.FC { const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning') const supportsNativeCSSAnchorPositioning = useRef(false) const cssAnchorPositioning = cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning.current + // Only use Popover API when both CSS anchor positioning is enabled AND usePopoverApi is true + const shouldUsePopover = cssAnchorPositioning && usePopoverApi const anchorRef = useProvidedRefOrCreate(externalAnchorRef) const [overlayRef, updateOverlayRef] = useRenderForcingRef() const anchorId = useId(externalAnchorId) @@ -282,14 +292,17 @@ export const AnchoredOverlay: React.FC { if (overlayProps?.ref) { assignRef(overlayProps.ref, node) From f68e82ba761ef777bdbf2748c6edb14d48f5ba9d Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Fri, 17 Apr 2026 13:47:46 -0400 Subject: [PATCH 2/9] Some clean up --- .../react/src/AnchoredOverlay/AnchoredOverlay.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index d7b29f816e5..decf20419db 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -122,11 +122,8 @@ interface AnchoredOverlayBaseProps extends Pick /** * When enabled (and CSS anchor positioning feature flag is on), uses the Popover API - * to render the overlay in the browser's top layer. This helps the overlay escape - * stacking contexts and appear above other elements like sticky headers. - * @default false */ - usePopoverApi?: boolean + usePopover?: boolean } export type AnchoredOverlayProps = AnchoredOverlayBaseProps & @@ -169,13 +166,13 @@ export const AnchoredOverlay: React.FC { const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning') const supportsNativeCSSAnchorPositioning = useRef(false) const cssAnchorPositioning = cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning.current - // Only use Popover API when both CSS anchor positioning is enabled AND usePopoverApi is true - const shouldUsePopover = cssAnchorPositioning && usePopoverApi + // Only use Popover API when both CSS anchor positioning is enabled AND usePopover is true + const shouldUsePopover = cssAnchorPositioning && usePopover const anchorRef = useProvidedRefOrCreate(externalAnchorRef) const [overlayRef, updateOverlayRef] = useRenderForcingRef() const anchorId = useId(externalAnchorId) @@ -292,7 +289,7 @@ export const AnchoredOverlay: React.FC Date: Fri, 17 Apr 2026 14:27:47 -0400 Subject: [PATCH 3/9] Add changeset --- .changeset/hot-phones-sing.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hot-phones-sing.md diff --git a/.changeset/hot-phones-sing.md b/.changeset/hot-phones-sing.md new file mode 100644 index 00000000000..2c078eddf7c --- /dev/null +++ b/.changeset/hot-phones-sing.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +AnchoredOverlay: (Behind feature flag) Make popover API usage opt-in From d9716f0a8fc326b83a62f0fd453af0fa15131fdd Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Mon, 20 Apr 2026 10:40:28 -0400 Subject: [PATCH 4/9] Update packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- 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 decf20419db..37f4009c175 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -121,7 +121,8 @@ interface AnchoredOverlayBaseProps extends Pick /** - * When enabled (and CSS anchor positioning feature flag is on), uses the Popover API + * When `true`, uses the Popover API only if the CSS anchor positioning feature flag is enabled + * and the browser supports native CSS anchor positioning. Has no effect otherwise. Defaults to `false`. */ usePopover?: boolean } From f7596e942f059223bff3b23490a2a1accac22f74 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Mon, 20 Apr 2026 10:54:43 -0400 Subject: [PATCH 5/9] Change prop name; add test --- .changeset/hot-phones-sing.md | 2 +- .../AnchoredOverlay/AnchoredOverlay.test.tsx | 60 +++++++++++++++++++ .../src/AnchoredOverlay/AnchoredOverlay.tsx | 20 +++---- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/.changeset/hot-phones-sing.md b/.changeset/hot-phones-sing.md index 2c078eddf7c..83c4be66599 100644 --- a/.changeset/hot-phones-sing.md +++ b/.changeset/hot-phones-sing.md @@ -1,5 +1,5 @@ --- -'@primer/react': patch +'@primer/react': minor --- AnchoredOverlay: (Behind feature flag) Make popover API usage opt-in diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx index 2c8e149e7b1..345f379317e 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx @@ -21,6 +21,7 @@ type TestComponentSettings = { className?: string withCSSAnchorPositioningFeatureFlag?: boolean overlayProps?: Pick + renderAs?: 'portal' | 'popover' } const AnchoredOverlayTestComponent = ({ @@ -31,6 +32,7 @@ const AnchoredOverlayTestComponent = ({ className, withCSSAnchorPositioningFeatureFlag, overlayProps, + renderAs, }: TestComponentSettings = {}) => { const [open, setOpen] = useState(initiallyOpen) const onOpen = useCallback( @@ -57,6 +59,7 @@ const AnchoredOverlayTestComponent = ({ renderAnchor={props => } onPositionChange={onPositionChange} className={className} + renderAs={renderAs} {...overlayProps} > @@ -303,6 +306,52 @@ describe('AnchoredOverlay feature flag specific behavior', () => { const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).toHaveAttribute('data-anchor-position', 'true') }) + + it('should set popover="manual" on overlay when renderAs is "popover"', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).toHaveAttribute('popover', 'manual') + }) + + it('should set popovertarget on anchor when renderAs is "popover"', () => { + const {baseElement} = render( + + + , + ) + + const anchor = baseElement.querySelector('[aria-haspopup="true"]') + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(anchor).toHaveAttribute('popovertarget') + expect(anchor!.getAttribute('popovertarget')).toBe(overlay!.getAttribute('id')) + }) + + it('should not set popover attribute on overlay when renderAs is "portal"', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).not.toHaveAttribute('popover') + }) + + it('should not set popover attribute on overlay when renderAs defaults to "portal"', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).not.toHaveAttribute('popover') + }) }) describe('with primer_react_css_anchor_positioning feature flag disabled', () => { @@ -329,5 +378,16 @@ describe('AnchoredOverlay feature flag specific behavior', () => { const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).toHaveAttribute('data-anchor-position', 'false') }) + + it('should not set popover attribute on overlay when renderAs is "popover" but flag is disabled', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).not.toHaveAttribute('popover') + }) }) }) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 37f4009c175..ec6efb87d73 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -124,7 +124,7 @@ interface AnchoredOverlayBaseProps extends Pick { const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning') const supportsNativeCSSAnchorPositioning = useRef(false) const cssAnchorPositioning = cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning.current - // Only use Popover API when both CSS anchor positioning is enabled AND usePopover is true - const shouldUsePopover = cssAnchorPositioning && usePopover + // Only use Popover API when both CSS anchor positioning is enabled AND renderAs is true + const shouldRenderAs = cssAnchorPositioning && renderAs === 'popover' const anchorRef = useProvidedRefOrCreate(externalAnchorRef) const [overlayRef, updateOverlayRef] = useRenderForcingRef() const anchorId = useId(externalAnchorId) @@ -290,8 +290,8 @@ export const AnchoredOverlay: React.FC { if (overlayProps?.ref) { assignRef(overlayProps.ref, node) From a51323ec65d8b7980583a884f63380d6ea3a37d6 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Wed, 22 Apr 2026 18:39:50 +0000 Subject: [PATCH 6/9] Address feedback --- packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index ec6efb87d73..057b4b3f5e2 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -121,8 +121,8 @@ interface AnchoredOverlayBaseProps extends Pick /** - * When `true`, uses the Popover API only if the CSS anchor positioning feature flag is enabled - * and the browser supports native CSS anchor positioning. Has no effect otherwise. Defaults to `false`. + * When `"popover"`, uses the Popover API only if the CSS anchor positioning feature flag is enabled + * and the browser supports native CSS anchor positioning. Has no effect otherwise. Defaults to `"portal"`. */ renderAs?: 'portal' | 'popover' } From 76c86345742cc5ccac08a358bb299419d892dfdf Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Wed, 22 Apr 2026 14:40:29 -0400 Subject: [PATCH 7/9] Update packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx Co-authored-by: Marie Lucca <40550942+francinelucca@users.noreply.github.com> --- packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 057b4b3f5e2..03b68ee7bf6 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -173,7 +173,7 @@ export const AnchoredOverlay: React.FC() const anchorId = useId(externalAnchorId) From f1137b94da7002bfba86de9de717536e845c3f04 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Wed, 22 Apr 2026 18:43:30 +0000 Subject: [PATCH 8/9] Add more `shouldRenderAsPopover` --- packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 31e799419c5..5723bf1600d 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -295,7 +295,7 @@ export const AnchoredOverlay: React.FC { if (overlayProps?.ref) { assignRef(overlayProps.ref, node) From 79a62ca07f4d3c15f4af240060574b91c82bc13f Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Wed, 22 Apr 2026 18:54:45 +0000 Subject: [PATCH 9/9] Add back disable --- packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 5723bf1600d..31b4b755b8f 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -304,6 +304,7 @@ export const AnchoredOverlay: React.FC