Skip to content

perf(PageHeader): replace 18 :has() selectors with hoisted data attributes#7901

Open
mattcosta7 wants to merge 1 commit into
mainfrom
perf/pageheader-has-selectors
Open

perf(PageHeader): replace 18 :has() selectors with hoisted data attributes#7901
mattcosta7 wants to merge 1 commit into
mainfrom
perf/pageheader-has-selectors

Conversation

@mattcosta7
Copy link
Copy Markdown
Contributor

Closes #

Replaces 18 :has() selectors in PageHeader with plain attribute selectors on the root by hoisting title-size and navigation-visibility state from descendants up to the <PageHeader> root at render time.

PageHeader is a top-of-page component; the cost matters less for invalidation frequency (rare DOM churn) and more for selector-matching breadth: the 12 title-size :has() rules + 4 nav-hidden :has() rules + 2 negation :has() rules all sit on the same subject (the root .PageHeader) and the engine has to re-test all of them on every descendant attribute change inside the header. With this PR, every rule on .PageHeader becomes a constant-time attribute lookup on the same element.

Approach:

Root walks React.Children.toArray(children) once to find the official TitleArea and Navigation children (by child.type, the same way the existing dev-warning code matches ContextArea/LeadingAction). It reads variant and hidden and emits:

  • data-title-size-variant / data-title-size-variant-{narrow,regular,wide} on the root (mirrors the existing data-size-variant* on TitleArea).
  • data-has-nav on the root when a Navigation child is present.
  • data-nav-hidden-{all,narrow,regular,wide} on the root, mirroring the existing data-hidden-* on Navigation.

The existing attributes on TitleArea and Navigation are kept untouched so any external selectors that rely on them continue to work.

The existing getHiddenDataAttributes helper grew an optional prefix parameter so the same logic produces the data-nav-hidden-* set without duplication.

Changelog

New

  • data-title-size-variant / data-title-size-variant-{narrow,regular,wide} on the PageHeader root, mirroring TitleArea's variant prop.
  • data-has-nav on the PageHeader root when a PageHeader.Navigation child is rendered.
  • data-nav-hidden-{all,narrow,regular,wide} on the PageHeader root, mirroring PageHeader.Navigation's hidden prop.

Changed

  • Internal: PageHeader.module.css no longer uses :has() (18 selectors removed).

Removed

Nothing public.

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

  • 23 PageHeader unit tests pass.
  • tsc --noEmit on packages/react is clean.
  • Stylelint + ESLint + Prettier clean on touched files.

VRT expectations: No change to rendered output for any usage that follows the documented PageHeader.TitleArea / PageHeader.Navigation API. Edge case: a consumer who renders their own custom element carrying data-component='TitleArea' (rather than using <PageHeader.TitleArea>) would no longer get the size styles, because the React-children walk matches by component identity. This mirrors the existing dev-warning code in Root, which already walks direct DOM children to find the TitleArea, so any existing wrapping that broke that warning also wouldn't have had the variant rules apply consistently. No usage in this repo is affected.

Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 8e4a4bd

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

@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 May 29, 2026
@github-actions
Copy link
Copy Markdown
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. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

Copy link
Copy Markdown
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 improves PageHeader selector performance by replacing subtree-scoped :has() CSS selectors with root-level data attributes derived during render.

Changes:

  • Hoists TitleArea variant and Navigation presence/hidden state onto the PageHeader root.
  • Replaces PageHeader :has() selectors with root attribute selectors.
  • Adds a patch changeset for @primer/react.
Show a summary per file
File Description
packages/react/src/PageHeader/PageHeader.tsx Adds render-time state hoisting and shared hidden-attribute prefix support.
packages/react/src/PageHeader/PageHeader.module.css Replaces :has() selectors with root data-attribute selectors.
.changeset/perf-pageheader-has-selectors.md Documents the PageHeader performance change as a patch release.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment on lines +59 to +67
for (const child of React.Children.toArray(children)) {
if (!React.isValidElement(child)) continue
if (child.type === TitleArea) {
titleVariant = (child.props as TitleAreaProps).variant ?? 'medium'
} else if (child.type === Navigation) {
hasNavigation = true
navigationHidden = (child.props as NavigationProps).hidden ?? false
}
}

// Hoist title size + navigation visibility off direct children onto the
// root so styling can use plain attribute selectors instead of `:has()`.
let titleVariant: TitleAreaProps['variant'] = 'medium'
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.

3 participants