Skip to main content

Selector compaction (:is() nesting)

When a &-less nested rule collapses onto its accumulated ancestor, Jess factors the common ancestor out once and wraps each multi-branch side in a single :is(...) — instead of repeating the whole prefix or cartesian-expanding it into one row per combination.

Mode

The child-list folds below are the collapseNesting: 'compact' style. The default flatten style, 'native' (what collapseNesting: true means), folds a child list only where every folded branch is a single compound of the same specificity, with no pseudo-element, namespace prefix or :scope, and only standard pseudo-classes that take no argument (besides :is(), :not(), :has() and :where()); other branches stay distributed. It reproduces native nesting's specificity, matching and invalid-selector behaviour, not its exact bytes. See the Less Selector Compaction page.

The rule​

Joining a &-less descendant onto its ancestor emits the ancestor once, then wraps each side in :is(...) only if it is a multi-branch comma list (a single selector joins plainly):

InputOutput
.a, .b { .c {…} }:is(.a, .b) .c {…}
.a { .c, .d {…} }.a :is(.c, .d) {…}
.a, .b { .c, .d {…} }:is(.a, .b) :is(.c, .d) {…} — one row
.a, .b { & .c {…} }.a .c, .b .c {…} — & nesting cartesian-expands
.a, .b {…}.a, .b {…} — a rule's own header stays a plain list

A &-based child is a different path: each & substitutes over the full cartesian ancestor list (no :is()).

Worked example​

#first #deux {
#fourth, #five, #six {
.seven, .eight > #nine { margin: 0; }
#ten { padding: 0; }
}
}
#first #deux :is(#fourth, #five, #six) :is(.seven, .eight > #nine) {
margin: 0;
}
#first #deux :is(#fourth, #five, #six) #ten {
padding: 0;
}

The common ancestor is written once per child rule instead of exploding into 3 × 2 selectors. This is the same compaction as the Less 5.x engine — see the Less Selector Compaction page for the details, and Extend for the separate extend-driven :is() grafting.

Specificity and :is() grouping (nesting & extend)​

Folding branches into :is(...) — from this nesting collapse or from extend's :is() grafting — changes how a browser scores the selector, because :is() does not score as zero. Per the CSS spec, the specificity of an :is() is the specificity of its most specific argument (Selectors Level 4 — specificity). So a low-specificity branch grouped with a high-specificity one inherits the group's maximum: :is(.a, #b) scores as the ID #b — (1,0,0) — for both branches.

Nesting collapse carries that group score into the join (flattened output):

.a, #b {
.c { color: red; }
}
:is(.a, #b) .c {
color: red;
}

:is(.a, #b) scores (1,0,0), so the whole selector scores (1,1,0) — the .a .c match now carries ID-level weight it would not have on its own. Extend's own :is() groups never do this: in every output mode they hold only alternatives of the same specificity, so .a > .c extended by #b renders .a > .c, .a > #b, each row keeping its own score.

Migration note vs. less.js 4.x. 4.x expanded these into a comma-separated cascade, each row keeping its own specificity; 5.x/Jess groups the multi-parent header into one :is() scored at the maximum:

Source4.x output (per-row specificity)5.x output
.a, #b { .c {} }.a .c (0,2,0), #b .c (1,1,0):is(.a, #b) .c — both (1,1,0)
.a > .c {} + #b:extend(.c all).a > .c (0,2,0), .a > #b (1,1,0).a > .c, .a > #b — per-row, as 4.x

When the grouped branches share specificity (the common all-classes case, :is(.a, .b)) nothing changes — the shift is observable only when branches of different specificity are grouped, where the lower-specificity branch inherits the group's higher score and can flip a close cascade 4.x resolved per-row. The nesting-collapse grouping is the flattened-output form: the multi-parent :is() appears in every flattened style, while a mixed-specificity child list is grouped only by 'compact'. The 5.x-default nested output keeps the multi-parent header a plain comma list. Extend's :is() grafting appears in both nested and flattened output, and keeps the 'native' specificity guard in every mode, 'compact' included.