perf(css): audit :has() selectors and add stylelint guard for Safari#7708
perf(css): audit :has() selectors and add stylelint guard for Safari#7708hectahertz wants to merge 2 commits intomainfrom
Conversation
|
|
There was a problem hiding this comment.
Pull request overview
Audits and hardens :has() selector usage across the Primer React monorepo to prevent Safari/WebKit performance regressions, adding lint guardrails and documenting safe usage patterns.
Changes:
- Add a Stylelint rule to disallow
:has()by default with a Safari performance warning message. - Add
stylelint-disableaudit comments to existing CSS Module files that already rely on:has(). - Remove redundant
:has([data-color-mode])selectors from@primer/styled-reactBaseStyles and document:has()guidance for contributors.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| stylelint.config.mjs | Disallows :has() via Stylelint rule with error message guidance. |
| packages/styled-react/src/components/BaseStyles.tsx | Removes :has([data-color-mode]) selectors from global BaseStyles styling. |
| packages/react/src/experimental/SelectPanel2/SelectPanel.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/TreeView/TreeView.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Timeline/Timeline.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/SegmentedControl/SegmentedControl.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/PageHeader/PageHeader.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Dialog/Dialog.module.css | Adds file-level Stylelint disable for audited body:has(...) legacy path. |
| packages/react/src/ButtonGroup/ButtonGroup.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Button/ButtonBase.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Breadcrumbs/Breadcrumbs.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/AvatarStack/AvatarStack.module.css | Extends existing disables to include the new :has() disallow rule. |
| packages/react/src/ActionList/Group.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/ActionList/ActionList.module.css | Extends existing disables to include the new :has() disallow rule. |
| .github/instructions/css.instructions.md | Documents :has() risks, safe patterns, and the required lint override approach. |
| */ | ||
| /* stylelint-disable-next-line selector-no-qualifying-type */ | ||
| /* stylelint-disable-next-line selector-no-qualifying-type, selector-pseudo-class-disallowed-list -- :has() on body guarded by feature flag negation, audited (github/github-ui#17224) */ | ||
| body:not([data-dialog-scroll-optimized]):has(.Dialog.DisableScroll) { |
There was a problem hiding this comment.
we can/should just ship the flag here that enables the optimization path soon!
I just keep missing the window's where it's unlocked to do that
#7633
There was a problem hiding this comment.
I've added #7633 to my list of “PRs to merge in the next release.” I’ll keep an eye on it and merge it the next time main is unlocked.
Closes https://github.com/github/github-ui/issues/17224
In Aug 2025, a
:has()selector on a broadly-present component caused 10-20+ second freezes on Safari due to quadratic style invalidation in WebKit (398 reactions, 86 participants, 446 points on HN). This PR audits all:has()usage in@primer/reactand adds guardrails to prevent a repeat.Audit summary: 46
:has()occurrences across 12 CSS Module files. All are low-risk because CSS Modules generate unique hashed class names, limiting the invalidation blast radius to a small subtree. The one dangerous pattern (body:has(...)in Dialog) was already mitigated with a feature flag guard.Changes:
Stylelint rule (
selector-pseudo-class-disallowed-list: ['has']) blocks new:has()by default. Developers must add astylelint-disablewith justification.File-level
stylelint-disablecomments on all 12 audited files, referencing the tracking issue.Removed redundant
:has()from@primer/styled-reactBaseStyles. Same:has([data-color-mode])pattern already removed from@primer/reactin #7325. The global selectors already handle input color-scheme.:has()guidance in CSS instructions (.github/instructions/css.instructions.md) covering safe vs dangerous patterns.Full audit with per-selector risk assessment posted on the tracking issue.
Changelog
New
:has()pseudo-class by default with Safari perf warningChanged
stylelint-disableaudit comments to 12 existing CSS files with:has()usage:has()guidanceRemoved
:has([data-color-mode])selectors from@primer/styled-reactBaseStyles (redundant with existing global selectors)Rollout strategy
Testing & Reviewing
:has()usage with clear error messagestylelint-disablecommentsnpm run lint:csspasses cleanMerge checklist
cc @mattcosta7