From 7d197575f22f7c6ff98d81c328aa576a15051e39 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Thu, 2 Apr 2026 14:27:26 -0400 Subject: [PATCH 1/7] Replace polyfill with `useAnchoredPosition` --- packages/react/package.json | 1 - .../src/AnchoredOverlay/AnchoredOverlay.tsx | 26 +++++-------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index d4ae30e6638..21c1ee30aa9 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -78,7 +78,6 @@ "@github/relative-time-element": "^4.5.0", "@github/tab-container-element": "^4.8.2", "@lit-labs/react": "1.2.1", - "@oddbird/css-anchor-positioning": "^0.9.0", "@oddbird/popover-polyfill": "^0.5.2", "@primer/behaviors": "^1.10.2", "@primer/live-region-element": "^0.7.1", diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 65af71b83dd..30502f672cd 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -125,17 +125,6 @@ export type AnchoredOverlayProps = AnchoredOverlayBaseProps & (AnchoredOverlayPropsWithAnchor | AnchoredOverlayPropsWithoutAnchor) & Partial> -const applyAnchorPositioningPolyfill = async () => { - if (typeof window !== 'undefined' && !('anchorName' in document.documentElement.style)) { - try { - await import('@oddbird/css-anchor-positioning') - } catch (e) { - // eslint-disable-next-line no-console - console.warn('Failed to load CSS anchor positioning polyfill:', e) - } - } -} - const defaultVariant = { regular: 'anchored', narrow: 'anchored', @@ -173,7 +162,9 @@ export const AnchoredOverlay: React.FC { - const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning') + const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning') + const supportsNativeCSSAnchorPositioning = useRef(false) + const cssAnchorPositioning = cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning.current const anchorRef = useProvidedRefOrCreate(externalAnchorRef) const [overlayRef, updateOverlayRef] = useRenderForcingRef() const anchorId = useId(externalAnchorId) @@ -232,19 +223,14 @@ export const AnchoredOverlay: React.FC { + supportsNativeCSSAnchorPositioning.current = 'anchorName' in document.documentElement.style + // ensure overlay ref gets cleared when closed, so position can reset between closing/re-opening if (!open && overlayRef.current) { updateOverlayRef(null) } - - if (cssAnchorPositioning && !hasLoadedAnchorPositioningPolyfill.current) { - applyAnchorPositioningPolyfill() - hasLoadedAnchorPositioningPolyfill.current = true - } - }, [open, overlayRef, updateOverlayRef, cssAnchorPositioning]) + }, [open, overlayRef, updateOverlayRef]) useFocusZone({ containerRef: overlayRef, From 2515e42235652e2c326f609c4557fe5e97b9d51f Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Thu, 2 Apr 2026 19:44:14 -0400 Subject: [PATCH 2/7] Adjust CSS --- packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css index 5edbdb0a043..8b503bfa74c 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css @@ -35,23 +35,27 @@ /* stylelint-disable primer/spacing */ top: calc(anchor(bottom) + var(--base-size-4)); left: anchor(left); + max-height: calc(100dvh - anchor(bottom) - var(--base-size-4)); } &[data-side='outside-top'] { margin-bottom: var(--base-size-4); bottom: anchor(top); left: anchor(left); + max-height: calc(anchor(top) - var(--base-size-4)); } &[data-side='outside-left'] { right: anchor(left); top: anchor(top); margin-right: var(--base-size-4); + max-height: calc(100dvh - anchor(top)); } &[data-side='outside-right'] { left: anchor(right); top: anchor(top); margin-left: var(--base-size-4); + max-height: calc(100dvh - anchor(top)); } } From 7dee64bd1b37f80eee39285c54d0c9f6640cb581 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Mon, 6 Apr 2026 11:56:13 -0400 Subject: [PATCH 3/7] Add some JS --- .../AnchoredOverlay.module.css | 11 ++++++++ .../src/AnchoredOverlay/AnchoredOverlay.tsx | 26 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css index 8b503bfa74c..a20a216393a 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css @@ -19,6 +19,7 @@ flip-inline, flip-block flip-inline; position-visibility: anchors-visible; + position-try-order: most-inline-size; z-index: 100; position: fixed !important; @@ -36,6 +37,11 @@ top: calc(anchor(bottom) + var(--base-size-4)); left: anchor(left); max-height: calc(100dvh - anchor(bottom) - var(--base-size-4)); + + &[data-align='left'] { + left: auto; + right: anchor(right); + } } &[data-side='outside-top'] { @@ -43,6 +49,11 @@ bottom: anchor(top); left: anchor(left); max-height: calc(anchor(top) - var(--base-size-4)); + + &[data-align='left'] { + left: auto; + right: anchor(right); + } } &[data-side='outside-left'] { diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 30502f672cd..810b4d9ec2c 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -268,6 +268,13 @@ export const AnchoredOverlay: React.FC= space.top ? 'bottom' : 'top' + const horizontal = space.right >= space.left ? 'right' : 'left' + + return {vertical, horizontal} +} + function assignRef( ref: React.MutableRefObject | ((instance: T | null) => void) | null | undefined, value: T | null, From 8125f088b3d921a776dead073bfba2072df271b7 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Mon, 6 Apr 2026 15:55:29 -0400 Subject: [PATCH 4/7] Try more lightweight solution --- .../src/AnchoredOverlay/AnchoredOverlay.module.css | 2 +- .../react/src/AnchoredOverlay/AnchoredOverlay.tsx | 13 +++++++++++-- packages/react/src/Overlay/Overlay.tsx | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css index a20a216393a..f9b98ee3d5e 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css @@ -40,7 +40,7 @@ &[data-align='left'] { left: auto; - right: anchor(right); + right: calc(anchor(right) - var(--anchored-overlay-anchor-offset-left)); } } diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 810b4d9ec2c..3c6d93e5d9b 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -16,6 +16,7 @@ import {XIcon} from '@primer/octicons-react' import classes from './AnchoredOverlay.module.css' import {clsx} from 'clsx' import {useFeatureFlag} from '../FeatureFlags' +import {widthMap} from '../Overlay/Overlay' interface AnchoredOverlayPropsWithAnchor { /** @@ -272,7 +273,15 @@ export const AnchoredOverlay: React.FC Date: Mon, 6 Apr 2026 17:28:27 -0400 Subject: [PATCH 5/7] Improve logic --- .../src/AnchoredOverlay/AnchoredOverlay.tsx | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 3c6d93e5d9b..48fda14a9a6 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -272,15 +272,12 @@ export const AnchoredOverlay: React.FC= overlayWidth) return null - const vertical = space.bottom >= space.top ? 'bottom' : 'top' - const horizontal = space.right >= space.left ? 'right' : 'left' + const horizontal = vw - rect.right >= rect.left ? 'right' : 'left' + const offset = overlayWidth - rect.left - return {vertical, horizontal} + return {horizontal, offset} } function assignRef( From 848c19c96b8d34c07e606806ea8d58fb4e92b515 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Mon, 6 Apr 2026 17:34:56 -0400 Subject: [PATCH 6/7] Add story to `dev` --- .../AnchoredOverlay.dev.stories.tsx | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx index 88f7a778309..a6746108422 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx @@ -4,7 +4,7 @@ import React, {useState, useRef} from 'react' import {Button} from '../Button' import {AnchoredOverlay} from '.' import {Stack} from '../Stack' -import {Dialog, Spinner, ActionList, ActionMenu} from '..' +import {Dialog, Spinner, ActionList, ActionMenu, Text} from '..' const meta = { title: 'Components/AnchoredOverlay/Dev', @@ -309,3 +309,50 @@ export const WithActionMenu = { }, }, } + +export const SmallViewportRightAligned = { + render: () => { + const [open, setOpen] = useState(false) + + return ( +
+ setOpen(true)} + onClose={() => setOpen(false)} + renderAnchor={props => } + overlayProps={{ + role: 'dialog', + 'aria-modal': true, + 'aria-label': 'Small viewport positioning test', + style: {minWidth: '320px'}, + }} + width="xlarge" + focusZoneSettings={{disabled: true}} + preventOverflow={false} + > +
+ + Overlay content + + This overlay is wider than the available space to the left of the anchor. It should reposition to avoid + overflowing the viewport. + + +
+
+
+ ) + }, + parameters: { + viewport: { + defaultViewport: 'small', + }, + docs: { + description: { + story: + 'Tests overlay positioning when the trigger button is right-aligned on a small viewport. The overlay is wider than the space to the left of the anchor.', + }, + }, + }, +} From cc8c8620aaf224cd692a0ed9e565dad32c3a8c7f Mon Sep 17 00:00:00 2001 From: TylerJDev <26746305+TylerJDev@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:43:06 +0000 Subject: [PATCH 7/7] chore: auto-fix lint and formatting issues --- packages/react/src/Overlay/Overlay.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/Overlay/Overlay.tsx b/packages/react/src/Overlay/Overlay.tsx index 4ae3e570654..9dcd41da021 100644 --- a/packages/react/src/Overlay/Overlay.tsx +++ b/packages/react/src/Overlay/Overlay.tsx @@ -33,7 +33,6 @@ export const heightMap = { 'fit-content': 'fit-content', } -// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-useless-assignment export const widthMap = { small: '256px', medium: '320px',