Skip to content

Graduate primer_react_css_has_selector_perf feature flag#7633

Open
Copilot wants to merge 4 commits intomainfrom
copilot/graduate-css-has-selector-flag
Open

Graduate primer_react_css_has_selector_perf feature flag#7633
Copilot wants to merge 4 commits intomainfrom
copilot/graduate-css-has-selector-flag

Conversation

Copy link
Contributor

Copilot AI commented Mar 6, 2026

  • Add changeset for graduating primer_react_css_has_selector_perf feature flag
Original prompt

Summary

Graduate the primer_react_css_has_selector_perf feature flag by making the CSS :has() performance optimization the default behavior and removing all flag-gating infrastructure.

Background

The primer_react_css_has_selector_perf flag was introduced to replace a costly CSS :has() selector (body:has(.Dialog.DisableScroll)) with a direct body attribute (body[data-dialog-scroll-disabled]) for disabling scroll when dialogs are open. This is a significant performance win. The flag has been validated and is ready to graduate (become the default, always-on behavior).

What needs to change

1. packages/react/src/FeatureFlags/DefaultFeatureFlags.ts

  • Remove primer_react_css_has_selector_perf: false from the default feature flags object.

2. packages/react/src/FeatureFlags/FeatureFlags.tsx

  • Remove the primer_react_css_has_selector_perf flag check (value.enabled('primer_react_css_has_selector_perf')).
  • Remove the dialogScrollOptimizedCount ref counting mechanism and the data-dialog-scroll-optimized body attribute management entirely (the comments in the code explicitly say this should be removed when graduating).
  • Remove the __resetDialogScrollOptimizedCount test helper function.
  • Remove the useIsomorphicLayoutEffect import if it's no longer needed.
  • The FeatureFlags component should go back to being a simple context provider.

3. packages/react/src/Dialog/Dialog.tsx

  • Remove the conditional check for document.body.hasAttribute('data-dialog-scroll-optimized') — always use the optimized path.
  • Always set data-dialog-scroll-disabled attribute on document.body when a dialog mounts.
  • Always remove data-dialog-scroll-disabled from document.body when no dialogs remain.
  • Remove the DisableScroll class usage from dialogs (dialog?.classList.add(classes.DisableScroll) and dialog?.classList.remove(classes.DisableScroll)) since the legacy CSS path is removed.
  • Keep --prc-dialog-scrollgutter CSS variable management.

4. packages/react/src/Dialog/Dialog.module.css

  • Remove the .DisableScroll class entirely.
  • Remove the legacy CSS rule: body:not([data-dialog-scroll-optimized]):has(.Dialog.DisableScroll) { ... }
  • Keep the optimized CSS rule: body[data-dialog-scroll-disabled] { ... }

5. packages/react/src/FeatureFlags/__tests__/FeatureFlags.test.tsx

  • Remove the entire describe('data-dialog-scroll-optimized attribute management') test block.
  • Remove the import of __resetDialogScrollOptimizedCount if present.

6. packages/react/src/Dialog/Dialog.test.tsx

  • Update the primer_react_css_has_selector_perf feature flag test block:
    • Tests should verify data-dialog-scroll-disabled is always set when dialog mounts and removed on unmount.
    • Remove tests that check for data-dialog-scroll-optimized attribute.
    • Remove tests that verify the "flag OFF" behavior since the optimization is now always on.
    • Keep/update multi-dialog ref counting tests but without feature flag wrapping.

Key principles

  • The body[data-dialog-scroll-disabled] CSS rule and attribute management should be the only scroll-disable mechanism.
  • No references to primer_react_css_has_selector_perf should remain in the codebase after this change.
  • No references to data-dialog-scroll-optimized should remain (that was the intermediary attribute for feature-flag gating).
  • The .DisableScroll CSS class and legacy :has() selector path should be fully removed.

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@changeset-bot
Copy link

changeset-bot bot commented Mar 6, 2026

🦋 Changeset detected

Latest commit: 94a5e2f

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 Patch

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

Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
Copilot AI changed the title [WIP] Graduate primer_react_css_has_selector_perf feature flag Graduate primer_react_css_has_selector_perf feature flag Mar 6, 2026
@mattcosta7 mattcosta7 marked this pull request as ready for review March 6, 2026 18:32
@mattcosta7 mattcosta7 requested a review from a team as a code owner March 6, 2026 18:32
Copilot AI review requested due to automatic review settings March 6, 2026 18:32
@mattcosta7 mattcosta7 requested a review from jonrohan March 6, 2026 18:32
@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Mar 6, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

⚠️ 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.

Copy link
Member

@jonrohan jonrohan left a comment

Choose a reason for hiding this comment

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

⚡ Awesome

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

Graduates the primer_react_css_has_selector_perf feature flag by making the Dialog scroll-disable mechanism always use body[data-dialog-scroll-disabled], and removes the temporary feature-flag scaffolding across FeatureFlags, Dialog, CSS, and tests.

Changes:

  • Removed primer_react_css_has_selector_perf from default feature flags and deleted all feature-flag gating infrastructure.
  • Updated Dialog to always set/remove data-dialog-scroll-disabled on document.body using a module-level ref counter.
  • Removed legacy :has()/.DisableScroll CSS path and simplified test coverage to validate the always-on behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/react/src/FeatureFlags/tests/FeatureFlags.test.tsx Removes tests and reset hooks related to the now-removed scroll optimization feature flag.
packages/react/src/FeatureFlags/FeatureFlags.tsx Simplifies FeatureFlags back to a pure context provider by removing temporary body-attribute management.
packages/react/src/FeatureFlags/DefaultFeatureFlags.ts Drops the graduated primer_react_css_has_selector_perf flag from defaults.
packages/react/src/Dialog/Dialog.tsx Makes data-dialog-scroll-disabled body attribute management unconditional and ref-counted at the Dialog level.
packages/react/src/Dialog/Dialog.test.tsx Replaces flag-conditional tests with a scroll disable behavior suite for the new always-on behavior.
packages/react/src/Dialog/Dialog.module.css Removes .DisableScroll and the legacy :has() selector rule, leaving only body[data-dialog-scroll-disabled].

Comment on lines 315 to 321
React.useEffect(() => {
const scrollbarWidth = window.innerWidth - document.body.clientWidth
const dialog = dialogRef.current
const usePerfOptimization = document.body.hasAttribute('data-dialog-scroll-optimized')

// Add DisableScroll class to this dialog (for legacy :has() selector path)
dialog?.classList.add(classes.DisableScroll)
dialogScrollDisabledCount++
document.body.style.setProperty('--prc-dialog-scrollgutter', `${scrollbarWidth}px`)

if (usePerfOptimization) {
// Optimized path: set attribute on body for direct CSS targeting
document.body.setAttribute('data-dialog-scroll-disabled', '')
}
// Legacy path: no action needed - CSS :has(.Dialog.DisableScroll) handles it
document.body.setAttribute('data-dialog-scroll-disabled', '')

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

--prc-dialog-scrollgutter is recomputed and set on every dialog mount. When a second dialog mounts after scroll has already been disabled, document.body.clientWidth will typically equal window.innerWidth (no scrollbar), making scrollbarWidth become 0 and overwriting the previously-correct gutter value. This can reintroduce layout shift while dialogs are still open. Consider only computing/setting the gutter (and the data-dialog-scroll-disabled attribute) when the ref count transitions from 0 → 1, and only removing them when it transitions back to 0.

See below for a potential fix:

    dialogScrollDisabledCount++
    if (dialogScrollDisabledCount === 1) {
      const scrollbarWidth = window.innerWidth - document.body.clientWidth
      document.body.style.setProperty('--prc-dialog-scrollgutter', `${scrollbarWidth}px`)
      document.body.setAttribute('data-dialog-scroll-disabled', '')
    }

Copilot uses AI. Check for mistakes.
Comment on lines 275 to 278
for (const footerButton of footerButtons) {
if (footerButton.autoFocus) {
// eslint-disable-next-line react-hooks/immutability
footerButton.ref = autoFocusedFooterButtonRef
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The footerButtons loop still mutates footerButton.ref, but the eslint-disable-next-line react-hooks/immutability was removed. If the repo’s immutability lint rule still applies here (it’s used elsewhere), this will fail linting. Either restore the targeted eslint-disable for this assignment or refactor to avoid mutating the footerButtons objects (e.g., derive a new array/object for rendering).

Copilot uses AI. Check for mistakes.
…f flag

Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants