Skip to content

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

Merged
mattcosta7 merged 10 commits into
mainfrom
perf/pageheader-has-selectors
Jul 7, 2026
Merged

perf(PageHeader): replace 18 :has() selectors with hoisted data attributes#7901
mattcosta7 merged 10 commits 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

changeset-bot Bot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 26b5941

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 thread packages/react/src/PageHeader/PageHeader.tsx Outdated
Comment thread packages/react/src/PageHeader/PageHeader.tsx Outdated
@primer

primer Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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

Comment thread packages/react/src/PageHeader/PageHeader.tsx Outdated
@github-actions github-actions Bot temporarily deployed to storybook-preview-7901 June 1, 2026 17:59 Inactive
@primer

primer Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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

@mattcosta7 mattcosta7 requested a review from Copilot June 9, 2026 12:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread packages/react/src/PageHeader/PageHeader.tsx
@github-actions github-actions Bot temporarily deployed to storybook-preview-7901 July 7, 2026 16:02 Inactive
@mattcosta7 mattcosta7 added the Canary Release Apply this label when you want CI to create a canary release of the current PR label Jul 7, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7901 July 7, 2026 19:40 Inactive
@mattcosta7 mattcosta7 added the integration-tests: passing Changes in this PR do NOT cause breaking changes in gh/gh label Jul 7, 2026
@mattcosta7 mattcosta7 enabled auto-merge July 7, 2026 20:08
@primer-integration

Copy link
Copy Markdown

⚠️ Hi from github/github-ui! The integration workflow could not find a canary version for the latest commit on this PR.

A successful canary CI run (i.e., a valid canary version published via the release.yml workflow) must exist for the latest commit before integration checks will succeed.

Next steps:

  1. Make sure the Canary Release label is applied to the PR — the release.yml workflow requires this label to publish a canary version.
  2. Wait for the release.yml canary CI run to complete successfully for the latest commit on this PR.
  3. Once a valid canary version exists, re-trigger the integration workflow by visiting the primer-react-pr-test workflow page, clicking Run workflow, and pasting this PR's URL.

For more details, see this workflow run.

@mattcosta7 mattcosta7 added integration-tests: skipped manually Changes in this PR do not require an integration test and removed integration-tests: passing Changes in this PR do NOT cause breaking changes in gh/gh labels Jul 7, 2026
@mattcosta7 mattcosta7 added this pull request to the merge queue Jul 7, 2026
Merged via the queue into main with commit 8288ed9 Jul 7, 2026
68 of 69 checks passed
@mattcosta7 mattcosta7 deleted the perf/pageheader-has-selectors branch July 7, 2026 20:19
@primer primer Bot mentioned this pull request Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canary Release Apply this label when you want CI to create a canary release of the current PR integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm integration-tests: skipped manually Changes in this PR do not require an integration test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants