Skip to content

AnchoredOverlay: Add support for CSS anchor positioning#7604

Open
TylerJDev wants to merge 39 commits intomainfrom
tylerjdev/css-anchor-positioning
Open

AnchoredOverlay: Add support for CSS anchor positioning#7604
TylerJDev wants to merge 39 commits intomainfrom
tylerjdev/css-anchor-positioning

Conversation

@TylerJDev
Copy link
Member

@TylerJDev TylerJDev commented Feb 26, 2026

Closes https://github.com/github/primer/issues/6446

The first batch of work to add CSS anchor positioning to overlay components.

Example of CSS anchor positioning:

Screen.Recording.2026-03-04.at.7.33.32.AM.mov
Video description

In the video, we show the `AnchoredOverlay` opening and being positioned directly under the trigger. We then show off the overlay repositioning when there's lack of space.

Changelog

New

  • Added support for CSS Anchor Positioning in AnchoredOverlay, gated by the primer_react_css_anchor_positioning feature flag.
  • Updated ActionMenu to ensure that the anchor button receives the correct anchor class for CSS anchor positioning, and improved merging of class names between anchor and button props.
  • Added new stories to AnchoredOverlay.features.stories.tsx to demonstrate anchor positioning in various layouts, including centered overlays, grid layouts, within dialogs (including nested and overflowing dialogs), scroll containers, and sticky headers.

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Merge checklist

@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2026

🦋 Changeset detected

Latest commit: 452d29e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added staff Author is a staff member integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm labels Feb 26, 2026
@github-actions
Copy link
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Or, apply the integration-tests: skipped manually label to skip these checks.

@primer
Copy link
Contributor

primer bot commented Feb 26, 2026

🤖 Lint and formatting issues have been automatically fixed and committed to this PR.

@github-actions github-actions bot temporarily deployed to storybook-preview-7604 February 26, 2026 22:32 Inactive
@primer
Copy link
Contributor

primer bot commented Feb 26, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@primer
Copy link
Contributor

primer bot commented Feb 26, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@primer
Copy link
Contributor

primer bot commented Feb 27, 2026

🤖 Lint and formatting issues have been automatically fixed and committed to this PR.

@github-actions github-actions bot temporarily deployed to storybook-preview-7604 February 27, 2026 02:10 Inactive
@primer
Copy link
Contributor

primer bot commented Feb 27, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@primer
Copy link
Contributor

primer bot commented Feb 27, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@github-actions github-actions bot temporarily deployed to storybook-preview-7604 March 3, 2026 21:53 Inactive
@github-actions github-actions bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Mar 3, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7604 March 3, 2026 22:13 Inactive
@github-actions github-actions bot temporarily deployed to storybook-preview-7604 March 3, 2026 22:35 Inactive
@TylerJDev TylerJDev added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Mar 3, 2026
@github-actions github-actions bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Mar 3, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7604 March 4, 2026 13:59 Inactive
@TylerJDev TylerJDev marked this pull request as ready for review March 4, 2026 22:13
@TylerJDev TylerJDev requested a review from a team as a code owner March 4, 2026 22:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds CSS Anchor Positioning support to AnchoredOverlay, gated behind the primer_react_css_anchor_positioning feature flag. When enabled, the overlay uses native CSS anchor positioning (with a polyfill for unsupported browsers) instead of JS-based positioning via a portal. This allows overlays within dialogs, sticky elements, and scroll containers to position correctly. The @oddbird/css-anchor-positioning polyfill is added as a runtime dependency.

Changes:

  • CSS Anchor Positioning feature flag and core logic added to AnchoredOverlay and Overlay components
  • ActionMenu updated to properly merge className from anchor props with user-provided class names
  • New Storybook stories demonstrating anchor positioning in various layouts (grids, dialogs, scroll containers, sticky headers)

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx Core logic: adds CSS anchor positioning path, polyfill invocation, Wrapper div, anchor/overlay class application
packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css New CSS classes: .Wrapper (anchor-scope), .Anchor (anchor-name), .AnchoredOverlay (anchor position + side-specific layout rules)
packages/react/src/Overlay/Overlay.tsx Skips portal when flag is on; sets data-anchor-position attribute for all Overlay instances
packages/react/src/Overlay/Overlay.module.css Gates top/left/right/bottom CSS var positioning on [data-anchor-position='false']
packages/react/src/FeatureFlags/DefaultFeatureFlags.ts Adds primer_react_css_anchor_positioning flag (default: false)
packages/react/src/ActionMenu/ActionMenu.tsx Merges anchor className (including classes.Anchor) with user-provided classNames for all anchor path types
packages/react/src/ActionMenu/ActionMenu.test.tsx Tests verify classes.Anchor is applied to anchor/button elements
packages/react/src/AnchoredOverlay/AnchoredOverlay.features.stories.tsx New stories demonstrating CSS anchor positioning in various layout scenarios
packages/react/src/AnchoredOverlay/AnchoredOverlay.features.stories.module.css New CSS for story layouts
packages/react/src/ActionMenu/ActionMenu.examples.stories.tsx New stories for centered ActionMenu and two-menu scenario
packages/react/package.json Adds @oddbird/css-anchor-positioning as a runtime dependency
package-lock.json Lock file updates for new dependencies
.changeset/nine-buttons-lose.md Minor release changeset for the feature
Comments suppressed due to low confidence (1)

packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx:241

  • When cssAnchorPositioning is enabled, the overlay becomes immediately visible (visibility={cssAnchorPositioning || position ? 'visible' : 'hidden'}), but useFocusTrap and useFocusZone are still gated on !open || !position. This means on the first render when position hasn't been calculated yet, the overlay is visible but the focus trap is not active. This can cause an accessibility gap where keyboard users can navigate out of the overlay before the focus trap activates.

The disabled condition for useFocusTrap and useFocusZone should be updated to account for CSS anchor positioning: when cssAnchorPositioning is true, the focus should be trapped as soon as the overlay is open (disabled: !open), rather than waiting for the JS-calculated position.

  useFocusZone({
    containerRef: overlayRef,
    disabled: !open || !position,
    ...focusZoneSettings,
  })
  useFocusTrap({containerRef: overlayRef, disabled: !open || !position, ...focusTrapSettings})

import {type ResponsiveValue} from '../hooks/useResponsiveValue'
import {IconButton, type IconButtonProps} from '../Button'
import {XIcon} from '@primer/octicons-react'
import polyfill from '@oddbird/css-anchor-positioning/fn'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean the polyfill is already part of the bundle?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! We had some of the polyfill's dependencies already (i.e., floating-ui), which is good since we're already accounting for those in our current bundle size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's interesting. I realised we no longer have a comment from the size action. Would be good to have a number before we merge.

It does run but not sure what the results are 😅

https://github.com/primer/react/actions/runs/22721918648/job/65886127997?pr=7604

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having Copilot take a stab at this here: #7628. Looks like we deleted the size.yml which added the comment. This adds it back 🤔

}, [open, overlayRef, updateOverlayRef])

if (cssAnchorPositioning && open) {
applyAnchorPositioningPolyfill()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we wait for open or load it already if we know we will need it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just load? 🤔 I'll go ahead and remove the open from the conditional!

bottom: var(--bottom, auto);
}

&[data-anchor-position='false']:not([data-responsive='fullscreen']) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bonus bugfix hiding in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, yeah - we explicitly set the top and left to 0 when fullscreen is used, so without this it would get overridden.

Copy link
Member

@siddharthkp siddharthkp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work!

The direction looks perfect, left a few clarifying questions

@TylerJDev
Copy link
Member Author

@siddharthkp - should be ready for another review! I addressed some feedback, and am working on the size report in #7628 😁

@primer-integration
Copy link

👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/15261

@primer-integration
Copy link

Integration test results from github/github-ui:

Failed  CI   Failed
Passed  VRT   Passed
Waiting  Projects   Waiting

CI check runs linting, type checking, and unit tests. Check the workflow logs for specific failures.

Need help? If you believe this failure is unrelated to your changes, please reach out to the Primer team for assistance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm staff Author is a staff member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants