Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/perf-segmentedcontrol-divider-no-has.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

SegmentedControl: improve rendering performance by removing an expensive `:has()` selector used for the inter-item divider. No visual or API changes.
53 changes: 38 additions & 15 deletions packages/react/src/SegmentedControl/SegmentedControl.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -169,23 +169,36 @@
&:not(:last-child) {
/* stylelint-disable-next-line primer/spacing */
margin-right: 1px;
}

&::after {
position: absolute;
top: var(--base-size-8);
right: calc(-1 * var(--base-size-2));
bottom: var(--base-size-8);
width: 1px;
content: '';
/* stylelint-disable-next-line primer/colors */
background-color: var(--borderColor-default);
}
/*
* Divider is drawn on the LEADING edge of each non-first item (rather than
* the trailing edge of each non-last item) so the "hide divider before the
* selected item" case can be expressed as a plain adjacent-sibling selector
* instead of `:has(+ [data-selected])`. Visual position is unchanged: the
* original `::after` at `right: -2px` (1px wide) on item N sat in the first
* 1px of item N+1's column (the previous item has `margin-right: 1px`), so
* the equivalent ::before on item N+1 lives at `left: 0`.
*/
&:not(:first-child)::before {
position: absolute;
top: var(--base-size-8);
left: 0;
bottom: var(--base-size-8);
width: 1px;
content: '';
/* stylelint-disable-next-line primer/colors */
background-color: var(--borderColor-default);
}

/* stylelint-disable-next-line selector-pseudo-class-disallowed-list -- scoped to CSS Module, audited (github/github-ui#17224) */
&:has(+ [data-selected])::after,
&:where([data-selected])::after {
background-color: transparent;
}
/*
* Hide the divider on the leading edge of the selected item itself. Uses a
* plain `[data-selected]` (not `:where()`) so specificity matches the divider
* rule above (0,2,1) and wins by source order; `:where()` would drop to
* (0,1,1) and lose, leaving the divider visible.
*/
&[data-selected]::before {
background-color: transparent;
}

&:first-child {
Expand All @@ -205,6 +218,16 @@
}
}

/*
* Hide the divider on the leading edge of the item that immediately follows
* the selected item. Adjacent-sibling combinator avoids the previous
* `:has(+ [data-selected])::after` which forced a sibling-direction `:has()`
* re-evaluation on every selection change.
*/
.Item[data-selected] + .Item::before {
background-color: transparent;
}

.Button {
/* TODO: use primitive `primer.control.medium.paddingInline.normal` when it is available */
--segmented-control-button-inner-padding: 12px;
Expand Down
Loading