Skip to main content

Advanced: extend

extend is the mirror image of a mixin. A mixin copies a block's declarations into your selector; extend does the opposite — it copies your selector up to another rule, so both share one block of CSS instead of duplicating it. It's the leanest way to say "style me like that thing, too."

The basic idea

Attach &:extend(<target>) inside a rule (or the Jess $extend <target>; statement), and your selector is added wherever the target already appears.

.animal { background: black; color: white; }
.bear {
$extend .animal; // style .bear like .animal
background: brown;
}

The extend clause itself is never emitted — it's resolved away, leaving your rule's own declarations behind. Matching runs against the compiled CSS (after nesting is resolved), not the source text.

Forms

// statement form (Jess-native)
.a { $extend .b; }

// multiple targets
.a { $extend .b, .c; }

In Less, the body form .a { &:extend(.b); } is exactly equivalent to attaching :extend(.b) to every selector of the rule.

Matching mode

Jess $extend .b; uses partial matching by default. It matches .b wherever that selector appears, then rewrites that spot in place.

.clearfix {
&:after { content: ''; clear: both; }
}
.foo { $extend .clearfix; color: red; } // partial (all) by default

Because .clearfix is matched inside the compound .clearfix:after, the match is grafted in place as :is(.clearfix, .foo):after.

:is() output

When several selectors extend into the same spot, Jess combines them into a single :is(…) group rather than duplicating the whole rule once per extender. This keeps the output compact:

.clearfix { &:after { content: ''; } }
.foo { $extend .clearfix; }
.bar { $extend .clearfix; }

When the target is the whole selector (not part of a compound), the extenders simply join the selector list — no :is() needed:

.clearfix,
.foo,
.bar {
/* … */
}

Chained extends

Extends resolve to a fixpoint: if .b extends .a and .c extends .b, then .c chains all the way through. Ordering doesn't matter — the extender may come before the target.

.a { color: black; }
.b { $extend .a; }
.c { $extend .b; }

Self-references and circular references are handled safely — they simply resolve and stop, never loop.

Exact matching with !exact

Most extends do not need !exact. Use it when the target must be the whole selector, not a piece inside a larger selector.

For $extend .animal;, these targets match differently:

Target ruleDefault $extend .animal;$extend .animal !exact;
.animalmatchesmatches
.animal:hovermatches .animal inside the compoundno match
.card .animalmatches .animal at the endno match
.animal.largematches .animal inside the compoundno match

!exact can still target a complex selector. In this example, .bear only joins the rule whose whole selector is .card .animal:

.card .animal { color: black; }
.panel .card .animal { color: gray; }
.card .animal:hover { color: brown; }

.bear { $extend .card .animal !exact; }
.card .animal,
.bear {
color: black;
}
.panel .card .animal {
color: gray;
}
.card .animal:hover {
color: brown;
}

Without !exact, the same target would also match the embedded .card .animal in the descendant and hover selectors.

Nested selectors

Extend matches nested selectors too, because it works on the compiled result:

.sidebar {
width: 300px;
.box { border: 1px solid; }
}
.sidebar2 { $extend .sidebar; background: blue; }

Extending inside @media

An extend written inside a @media block only matches selectors in that same media (or a nested one) — it won't reach the top level or a sibling media. A top-level extend reaches everything, including inside media blocks.

.ext1 .outside { color: inherit; }   // outside media — not matched from inside

@media (tv) {
.ext1 .inside { color: black; }
.tv-only {
$extend .ext1; // matches only within this media
background: blue;
}
}

Nested @media blocks stay nested in Jess's output — they are not merged.

Reference-mode visibility

A @compose (reference) / @import (reference) module hides its own rules from the output. If your extender targets a selector that lives in a referenced module, only your selector surfaces — carrying the referenced rule's declarations — while the referenced target header itself is never emitted. Extend is the sanctioned way to pull styles out of a reference-imported sheet.

What extend does not do

  • It is selector-level, not property-level. Extend shares a rule's whole declaration block. It cannot cherry-pick a single property — reach for a mixin or a variable for that.
  • It does not match variable targets. An interpolated selector used as a match target matches nothing. (An interpolated selector as the extender is fine.)
  • It does not normalize the target form. Matching is exact: a leading star, the order of pseudo-classes, and the nth form all matter. Only attribute-selector quote type is normalized.

When to reach for extend

  • Avoid a base class. Style .bear like .animal without adding class="animal bear" to your markup.
  • Shrink your CSS. A mixin copies declarations into every caller; extend moves the selectors up to one shared block, so the declarations appear once.
  • Relate two unrelated blocks. Mixins only take simple selectors; extend can graft complex selectors together (button.list-style sharing li.list > a's styles).
note

The full behavior — exact-match strictness, the fixpoint termination rules, :is() grafting, and @media scoping — is documented for contributors in docs/architecture/core/EXTEND-SEMANTICS.md.