Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 25, 2025

Adds primer_react_css_perf_has_selector feature flag to opt-in to skipping expensive :has([data-color-mode]) selectors that scan the entire DOM on every style recalculation.

Changes

Modified BaseStyles.module.css to wrap expensive selectors with :not([data-primer-css-perf-has-selector]):

/* Before: Always active */
&:has([data-color-mode='light']) {
  input & {
    color-scheme: light;
  }
}

/* After: Only active when flag NOT present */
&:not([data-primer-css-perf-has-selector]):has([data-color-mode='light']) {
  input & {
    color-scheme: light;
  }
}

Integrated with Primer React's feature flag system:

  • Added primer_react_css_perf_has_selector to DefaultFeatureFlags (defaults to false)
  • BaseStyles component uses useFeatureFlag hook to automatically add the data-primer-css-perf-has-selector attribute when enabled
  • No manual attribute management required

Usage:

import {FeatureFlags, BaseStyles} from '@primer/react'

function App() {
  return (
    <FeatureFlags flags={{primer_react_css_perf_has_selector: true}}>
      <BaseStyles>
        <YourApp />
      </BaseStyles>
    </FeatureFlags>
  )
}

Default behavior: Expensive selectors remain active (unchanged)
Opt-in behavior: Enable the primer_react_css_perf_has_selector feature flag to automatically apply the optimization

Input color-scheme is already handled by global selectors at lines 20-26, making these :has() checks redundant for consumers who opt-in.

Changelog

New

  • Added primer_react_css_perf_has_selector feature flag to control performance optimization

Changed

  • Modified .BaseStyles selector to support performance feature flag via negative data attribute check
  • BaseStyles component now uses useFeatureFlag hook to automatically add optimization attribute
  • Added documentation comments explaining the performance optimization and feature flag behavior

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

Consumers can test the optimization in staging/canary by enabling the feature flag before production rollout. Once validated broadly, the feature flag can be removed in a future release.

Testing & Reviewing

Added tests to verify feature flag integration:

  • Test confirms attribute is NOT present by default (backward compatible)
  • Test confirms attribute IS present when feature flag is enabled

To test the optimization manually:

  1. Enable the primer_react_css_perf_has_selector feature flag via FeatureFlags component
  2. Verify input color-scheme still works correctly (handled by global selectors)
  3. Use Chrome DevTools Performance tab to validate :has() selectors are no longer recalculating styles

Merge checklist

Original prompt

Summary

Create a feature-flagged version of the performance optimization from #7325. Instead of removing the expensive :has([data-color-mode]) selectors outright, use a negative data attribute check to allow consumers to opt-in to the optimization.

This allows us to ship the change safely by giving consumers control over when to enable the optimization.

Implementation

In packages/react/src/BaseStyles.module.css, modify the .BaseStyles class to use a negative data attribute selector as a feature flag:

.BaseStyles {
  font-family: var(--BaseStyles-fontFamily, var(--fontStack-system));
  /* stylelint-disable-next-line primer/typography */
  line-height: var(--BaseStyles-lineHeight, 1.5);
  /* stylelint-disable-next-line primer/colors */
  color: var(--BaseStyles-fgColor, var(--fgColor-default));

  /*
   * PERFORMANCE: The :has([data-color-mode]) selectors below are expensive
   * as they scan the entire DOM on every style recalculation.
   * Input color-scheme is already handled by global selectors above:
   *   [data-color-mode='light'] input { color-scheme: light; }
   *   [data-color-mode='dark'] input { color-scheme: dark; }
   *
   * Feature flag: When [data-primer-css-perf-has-selector] is NOT present on
   * an ancestor element, the old (expensive) behavior is preserved.
   * Add [data-primer-css-perf-has-selector] to an ancestor to opt-in to the
   * optimized behavior (which skips these expensive selectors).
   *
   * See #7325 and #7312 for context on this performance optimization.
   */

  /* Global styles for light mode - only apply when feature flag is NOT present */
  &:not([data-primer-css-perf-has-selector] *):has([data-color-mode='light']) {
    input & {
      color-scheme: light;
    }
  }

  /* Global styles for dark mode - only apply when feature flag is NOT present */
  &:not([data-primer-css-perf-has-selector] *):has([data-color-mode='dark']) {
    input & {
      color-scheme: dark;
    }
  }

  /* Low-specificity default link styling */
  :where(a:not([class*='prc-']):not([class*='PRC-']):not([class*='Primer_Brand__'])) {
    color: var(--fgColor-accent, var(--color-accent-fg));
    text-decoration: none;

    &:hover {
      text-decoration: underline;
    }
  }
}

How it works

  • Default (no attribute): The expensive :has([data-color-mode]) selectors remain active - preserving existing behavior.
  • Opt-in (attribute present): When data-primer-css-perf-has-selector is added to any ancestor element (e.g., <html data-primer-css-perf-has-selector> or <body data-primer-css-perf-has-selector>), the expensive selectors are bypassed.

Changeset

Add a changeset file .changeset/perf-basestyles-has-selector-feature-flag.md:

---
'@primer/react': patch
---

perf(BaseStyles): Feature-flag expensive :has([data-color-mode]) selectors

Add a feature flag (`data-primer-css-perf-has-selector` data attribute) to opt-in to skipping expensive `:has([data-color-mode])` selectors that scan the entire DOM on every style recalculation.

To enable the optimization, add `data-primer-css-perf-has-selector` to an ancestor element (e.g., `<html>` or `<body>`). Input color-scheme is already handled by global selectors in the codebase.

See #7325 and #7312 for context on this performance optimization.

Why this approach?

  1. Safe rollout: Consumers can test the optimization in staging/canary environments before enabling in production.
  2. Backward compatible: Default behavior is unchanged.
  3. Easy to enable: Just add a single data attribute to enable the optimization.
  4. Easy to remove: Once validated, we can remove the feature flag and the expensive selectors entirely (like in perf(BaseStyles): Remove expensive :has([data-color-mode]) selectors #7325).

Related

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 Dec 25, 2025

🦋 Changeset detected

Latest commit: 2681380

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

- Modified BaseStyles.module.css to use negative data attribute selector
- Added comprehensive comments explaining the feature flag behavior
- Created changeset for the patch release
- All tests passing, linting and type checking successful

Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
Copilot AI changed the title [WIP] Add feature-flagged version of performance optimization perf(BaseStyles): Add feature flag for :has([data-color-mode]) selector optimization Dec 25, 2025
Copilot AI requested a review from mattcosta7 December 25, 2025 06:08
@mattcosta7
Copy link
Contributor

@copilot could we avoid an * selector by putting the attribute on the basestyle element when the flag is enabled?

Changed selector from `&:not([data-primer-css-perf-has-selector] *)` to
`&:not([data-primer-css-perf-has-selector])` to avoid the universal
selector. The attribute should now be placed directly on the BaseStyles
element instead of an ancestor.

Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 25, 2025

@copilot could we avoid an * selector by putting the attribute on the basestyle element when the flag is enabled?

Good catch! Changed to &:not([data-primer-css-perf-has-selector]) to avoid the universal selector. The attribute should now be placed directly on the BaseStyles element. (7306726)

@mattcosta7
Copy link
Contributor

@copilot use the feature flag system to automatically add it

- Added primer_react_css_perf_has_selector to DefaultFeatureFlags
- BaseStyles component now uses useFeatureFlag hook
- Automatically adds data-primer-css-perf-has-selector when flag is enabled
- Added tests to verify feature flag behavior
- Updated documentation to reference feature flag usage

Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 25, 2025

@copilot use the feature flag system to automatically add it

Integrated with the feature flag system! Added primer_react_css_perf_has_selector to DefaultFeatureFlags and BaseStyles now uses useFeatureFlag to automatically add the attribute when enabled. (2681380)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants