Sass: @extend Directive
@extend Directive
Sass’s @extend
rule tells Sass that one selector should inherit the styles of another.
It’s written @extend <selector>
.
Example:
.error {
border: 1px #f00;
background-color: #fdd;
&--serious {
@extend .error;
border-width: 3px;
}
}
and the generated style.css output
.error, .error--serious {
border: 1px #f00;
background-color: #fdd;
}
.error--serious {
border-width: 3px;
}
When one class extends another, Sass styles all elements that match the extender as though they also match the class being extended.
Sass knows to extend everywhere the selector is used. This ensures that your elements are styled exactly as if they matched the extended selector.
For example:
.error:hover {
background-color: #fee;
}
.error--serious {
@extend .error;
border-width: 3px;
}
and the generated style.css output
.error:hover, .error--serious:hover {
background-color: #fee;
}
.error--serious {
border-width: 3px;
}
Extends are resolved after the rest of your stylesheet is compiled. In particular, it happens after parent selectors are resolved. This means that if you @extend .error
, it won’t affect the inner selector in .error { &__icon { ... } }
. It also means that parent selectors in SassScript can’t see the results of extend.
How It Works
Unlike mixins, which copy styles into the current style rule, @extend
updates style rules that contain the extended selector so that they contain the extending selector as well. When extending selectors, Sass does intelligent unification:
- It never generates selectors like
#main#footer
that can’t possibly match any elements. - It ensures that complex selectors are interleaved so that they work no matter which order the HTML elements are nested.
- It trims redundant selectors as much as possible, while still ensuring that the specificity is greater than or equal to that of the extender.
- It knows when one selector matches everything another does, and can combine them together.
- It intelligently handles combinators, universal selectors, and pseudo-classes that contain selectors.
Example:
.content nav.sidebar {
@extend .info;
}
// This won't be extended, because `p` is incompatible with `nav`.
p.info {
background-color: #dee9fc;
}
// There's no way to know whether `<div class="guide">` will be inside or
// outside `<div class="content">`, so Sass generates both to be safe.
.guide .info {
border: 1px solid rgba(#000, 0.8);
border-radius: 2px;
}
// Sass knows that every element matching "main.content" also matches ".content"
// and avoids generating unnecessary interleaved selectors.
main.content .info {
font-size: 0.8em;
}
and the generated style.css output
p.info {
background-color: #dee9fc;
}
.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar {
border: 1px solid rgba(0, 0, 0, 0.8);
border-radius: 2px;
}
main.content .info, main.content nav.sidebar {
font-size: 0.8em;
}
Because @extend
updates style rules that contain the extended selector, their styles have precedence in the cascade based on where the extended selector’s style rules appear, not based on where the @extend
appears. This can be confusing, but just remember: this is the same precedence those rules would have if you added the extended class to your HTML!
Placeholder Selectors
Sometimes you want to write a style rule that’s only intended to be extended. In that case, you can use placeholder selectors, which look like class selectors that start with %
instead of .
. Any selectors that include placeholders aren’t included in the CSS output, but selectors that extend them are.
Example:
.alert:hover, %strong-alert {
font-weight: bold;
}
%strong-alert:hover {
color: red;
}
and the generated style.css output
.alert:hover {
font-weight: bold;
}
A placeholder selector can be marked private by starting its name with either -
or _
. A private placeholder selector can only be extended within the stylesheet that defines it. To any other stylesheets, it will look as though that selector doesn’t exist.
Extension Scope
When one stylesheet extends a selector, that extension will only affect style rules written in upstream modules—that is, modules that are loaded by that stylesheet using the @use
rule or the @forward
rule, modules loaded by those modules, and so on. This helps make your @extend
rules more predictable, ensuring that they affect only the styles you were aware of when you wrote them.
Extensions aren’t scoped at all if you’re using the @import
rule. Not only will they affect every stylesheet you import, they’ll affect every stylesheet that imports your stylesheet, everything else those stylesheets import, and so on. Without @use
, extensions are global.
Mandatory and Optional Extends
Normally, if an @extend
does not match any selectors in the stylesheet, Sass will produce an error. This helps protect from typos or from renaming a selector without renaming the selectors that inherit from it. Extends that require that the extended selector exists are mandatory.
This may not always be what you want, though. If you want the @extend
to do nothing if the extended selector does not exist, just add !optional
to the end.
Extends or Mixins?
Extends and mixins are both ways of encapsulating and re-using styles in Sass, which naturally raises the question of when to use which one. Mixins are obviously necessary when you need to configure the styles using arguments, but what if they’re just a chunk of styles?
Extends are the best option when you’re expressing a relationship between semantic classes (or other semantic selectors). Because an element with class .error--serious
is an error, it makes sense for it to extend .error
. But for non-semantic collections of styles, writing a mixin can avoid cascade headaches and make it easier to configure down the line.