Skip to content

perf(Dialog): replace :has(.Footer) with data-has-footer attribute#7599

Open
hectahertz wants to merge 5 commits intomainfrom
hectahertz/perf-dialog-css-has-selector
Open

perf(Dialog): replace :has(.Footer) with data-has-footer attribute#7599
hectahertz wants to merge 5 commits intomainfrom
hectahertz/perf-dialog-css-has-selector

Conversation

@hectahertz
Copy link
Contributor

@hectahertz hectahertz commented Feb 26, 2026

Closes #

Replaces .Dialog:has(.Footer) with .Dialog[data-has-footer] for the scrollable body border detection rule.

Dialog already resolves whether a footer exists in JS via useSlots + renderFooter, but wasn't exposing it as a data attribute. This PR adds data-has-footer on the dialog div and swaps the :has() selector for an attribute check.

The more expensive scroll-lock :has() selector (body:has(.Dialog.DisableScroll)) is already guarded behind a feature flag (primer_react_css_has_selector_perf) and gated with :not([data-dialog-scroll-optimized]), so it's not addressed here.

Other open perf PRs: #7598, #7597, #7554, #7550, #7549, #7545, #7544

Changelog

New

N/A

Changed

  • Dialog CSS now uses [data-has-footer] attribute selector instead of :has(.Footer) for footer border detection

Removed

N/A

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

One new data-has-footer attribute on the dialog div, one CSS selector change. The animation-timeline: scroll(self) border logic is unchanged.

  1. Open Storybook, navigate to Dialog stories
  2. Open a dialog with footer buttons, verify the scroll border appears between body and footer when content overflows
  3. Open a dialog without footer, verify no border artifact

Merge checklist

Copilot AI review requested due to automatic review settings February 26, 2026 12:11
@hectahertz hectahertz requested a review from a team as a code owner February 26, 2026 12:11
@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2026

🦋 Changeset detected

Latest commit: 2313366

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

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 improves Dialog CSS performance by avoiding a parent :has() selector for footer detection and instead exposing footer presence via a data-has-footer attribute on the Dialog root.

Changes:

  • Add data-has-footer to the Dialog root element when a footer is rendered.
  • Replace .Dialog:has(.Footer) with .Dialog[data-has-footer] in Dialog CSS for the scroll border rule.
  • Add a changeset documenting the patch change.

Reviewed changes

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

File Description
packages/react/src/Dialog/Dialog.tsx Adds data-has-footer attribute derived from the computed footer node.
packages/react/src/Dialog/Dialog.module.css Updates footer border detection selector to use [data-has-footer] instead of :has(.Footer).
.changeset/perf-dialog-css-has-selector.md Adds a patch changeset entry describing the selector/attribute change.

{...positionDataAttributes}
data-width={width}
data-height={height}
data-has-footer={footer ? '' : undefined}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

data-has-footer is derived from footer ? ... which uses a truthiness check. Since footer is a ReactNode, valid values like 0 or '' would incorrectly omit the attribute. Prefer a nullish check (e.g. set the attribute when footer != null) to match “footer rendered” semantics more reliably.

Suggested change
data-has-footer={footer ? '' : undefined}
data-has-footer={footer != null ? '' : undefined}

Copilot uses AI. Check for mistakes.
{...positionDataAttributes}
data-width={width}
data-height={height}
data-has-footer={footer ? '' : undefined}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

A new DOM contract is introduced (data-has-footer) and drives styling, but there are no unit tests asserting the attribute is present when a footer is rendered and absent when renderFooter/footer slot returns null. Adding a couple of assertions in the existing Dialog test suite would help prevent regressions.

Copilot uses AI. Check for mistakes.
@primer-integration
Copy link

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

@primer-integration
Copy link

Integration test results from github/github-ui:

Passed  CI   Passed
Passed  VRT   Passed
Passed  Projects   Passed

All checks passed!

Copy link
Member

@francinelucca francinelucca left a comment

Choose a reason for hiding this comment

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

mostly LGTM, just was wondering about the TextInput file

const [isOverLimit, setIsOverLimit] = useState<boolean>(false)
const [screenReaderMessage, setScreenReaderMessage] = useState<string>('')
const characterCounterRef = useRef<CharacterCounter | null>(null)
const lastCountedLengthRef = useRef<number | null>(null)
Copy link
Member

Choose a reason for hiding this comment

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

what are these changes? 😅 seems unrelated to the Dialog stuff? if so can we separate into a different pr?

@hectahertz
Copy link
Contributor Author

Good catch! Removed those, they already shipped in #7600.

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