mirror of
https://github.com/danog/sass-site.git
synced 2024-12-14 02:17:36 +01:00
434 lines
14 KiB
Plaintext
434 lines
14 KiB
Plaintext
---
|
|
title: "@extend"
|
|
table_of_contents: true
|
|
introduction: >
|
|
There are often cases when designing a page when one class should have all the
|
|
styles of another class, as well as its own specific styles. For example, the
|
|
[BEM methodology](http://getbem.com/naming/) encourages modifier classes that
|
|
go on the same elements as block or element classes. But this can create
|
|
cluttered HTML, it's prone to errors from forgetting to include both classes,
|
|
and it can bring non-semantic style concerns into your markup.
|
|
---
|
|
|
|
<!-- TODO(jina): I think these code blocks should be side-by-side -->
|
|
{% markdown %}
|
|
```html
|
|
<div class="error error--serious">
|
|
Oh no! You've been hacked!
|
|
</div>
|
|
```
|
|
|
|
```css
|
|
.error {
|
|
border: 1px #f00;
|
|
background-color: #fdd;
|
|
}
|
|
|
|
.error--serious {
|
|
border-width: 3px;
|
|
}
|
|
```
|
|
|
|
Sass's `@extend` rule solves this. It's written `@extend <selector>`, and it
|
|
tells Sass that one selector should inherit the styles of another.
|
|
{% endmarkdown %}
|
|
|
|
{% codeExample 'extend' %}
|
|
.error {
|
|
border: 1px #f00;
|
|
background-color: #fdd;
|
|
|
|
&--serious {
|
|
@extend .error;
|
|
border-width: 3px;
|
|
}
|
|
}
|
|
===
|
|
.error
|
|
border: 1px #f00
|
|
background-color: #fdd
|
|
|
|
&--serious
|
|
@extend .error
|
|
border-width: 3px
|
|
{% endcodeExample %}
|
|
|
|
{% markdown %}
|
|
When one class extends another, Sass styles all elements that match the
|
|
extender as though they also match the class being extended. When one class
|
|
selector extends another, it works exactly as though you added the extended
|
|
class to every element in your HTML that already had the extending class. You
|
|
can just write `class="error--serious"`, and Sass will make sure it's styled
|
|
as though it had `class="error"` as well.
|
|
|
|
Of course, selectors aren't just used on their own in style rules. Sass knows
|
|
to extend *everywhere* the selector is used. This ensures that your elements
|
|
are styled exactly as if they matched the extended selector.
|
|
{% endmarkdown %}
|
|
|
|
{% codeExample 'extended-selector' %}
|
|
.error:hover {
|
|
background-color: #fee;
|
|
}
|
|
|
|
.error--serious {
|
|
@extend .error;
|
|
border-width: 3px;
|
|
}
|
|
===
|
|
.error:hover
|
|
background-color: #fee
|
|
|
|
|
|
.error--serious
|
|
@extend .error
|
|
border-width: 3px
|
|
{% endcodeExample %}
|
|
|
|
{% headsUp %}
|
|
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.
|
|
|
|
[parent selectors]: /documentation/style-rules/parent-selector
|
|
[parent selectors in SassScript]: /documentation/style-rules/parent-selector#in-sassscript
|
|
{% endheadsUp %}
|
|
|
|
{% markdown %}
|
|
## 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*:
|
|
|
|
[mixins]: /documentation/at-rules/mixin
|
|
|
|
* 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][].
|
|
|
|
[combinators]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#Combinators
|
|
[pseudo-classes that contain selectors]: https://developer.mozilla.org/en-US/docs/Web/CSS/:not
|
|
[universal selectors]: https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors
|
|
{% endmarkdown %}
|
|
|
|
{% codeExample 'how-it-works' %}
|
|
.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;
|
|
}
|
|
===
|
|
.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
|
|
{% endcodeExample %}
|
|
|
|
{% funFact %}
|
|
You can directly access Sass's intelligent unification using [selector
|
|
functions][]! The [`selector.unify()` function][] returns a selector that
|
|
matches the intersection of two selectors, while the [`selector.extend()`
|
|
function][] works just like `@extend`, but on a single selector.
|
|
|
|
[selector functions]: /documentation/modules/selector
|
|
[`selector.unify()` function]: /documentation/modules/selector#unify
|
|
[`selector.extend()` function]: /documentation/modules/selector#extend
|
|
{% endfunFact %}
|
|
|
|
{% headsUp %}
|
|
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!
|
|
|
|
[the cascade]: https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade
|
|
{% endheadsUp %}
|
|
|
|
{% markdown %}
|
|
## 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.
|
|
|
|
[placeholder selectors]: /documentation/style-rules/placeholder-selectors
|
|
{% endmarkdown %}
|
|
|
|
{% render 'code-snippets/example-placeholder' %}
|
|
|
|
{% markdown %}
|
|
### Private Placeholders
|
|
|
|
Like [module members][], 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.
|
|
|
|
[module members]: /documentation/at-rules/use#private-members
|
|
|
|
## 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.
|
|
|
|
[`@use` rule]: /documentation/at-rules/use
|
|
[`@forward` rule]: /documentation/at-rules/forward
|
|
|
|
{% headsUp %}
|
|
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*.
|
|
|
|
[`@import` rule]: /documentation/at-rules/import
|
|
{% endheadsUp %}
|
|
|
|
## Mandatory and Optional Extends
|
|
|
|
Normally, if an `@extend` doesn't 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 doesn't 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?
|
|
|
|
[mixins]: /documentation/at-rules/mixin
|
|
[arguments]: /documentation/at-rules/mixin#arguments
|
|
|
|
As a rule of thumb, 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.
|
|
|
|
{% funFact %}
|
|
Most web servers compress the CSS they serve using an algorithm that's very
|
|
good at handling repeated chunks of identical text. This means that,
|
|
although mixins may produce more CSS than extends, they probably won't
|
|
substantially increase the amount your users need to download. So choose the
|
|
feature that makes the most sense for your use-case, not the one that
|
|
generates the least CSS!
|
|
|
|
[gzip]: https://en.wikipedia.org/wiki/Gzip
|
|
{% endfunFact %}
|
|
|
|
## Limitations
|
|
|
|
### Disallowed Selectors
|
|
{% endmarkdown %}
|
|
|
|
{% # Arguments are (in order): `dart`, `libsass`, `node`, `ruby`, optional feature name, additional details within %}
|
|
{% compatibility true, false, null, false, "No Compound Extensions" %}
|
|
LibSass and Ruby Sass currently allow compound selectors like `.message.info`
|
|
to be extended. However, this behavior doesn't match the definition of
|
|
`@extend`: instead of styling elements that match the extending selector as
|
|
though it had `class="message info"`, which would be affected by style rules
|
|
that included either `.message` *or* `.info`, it only styled them with rules
|
|
that included both `.message` *and* `info`.
|
|
|
|
In order to keep the definition of `@extend` straightforward and
|
|
understandable, and to keep the implementation clean and efficient, that
|
|
behavior is now deprecated and will be removed from future versions.
|
|
|
|
See [the breaking change page][] for more details.
|
|
|
|
[the breaking change page]: /documentation/breaking-changes/extend-compound
|
|
{% endcompatibility %}
|
|
|
|
{% markdown %}
|
|
Only *simple selectors*—individual selectors like `.info` or `a`—can be
|
|
extended. If `.message.info` could be extended, the definition of `@extend`
|
|
says that elements matching the extender would be styled as though they
|
|
matched `.message.info`. That's just the same as matching both `.message` and
|
|
`.info`, so there wouldn't be any benefit in writing that instead of `@extend
|
|
.message, .info`.
|
|
|
|
Similarly, if `.main .info` could be extended, it would do (almost) the same
|
|
thing as extending `.info` on its own. The subtle differences aren't worth the
|
|
confusion of looking like it's doing something substantially different, so
|
|
this isn't allowed either.
|
|
{% endmarkdown %}
|
|
|
|
{% codeExample 'disallowed-selectors', false %}
|
|
.alert {
|
|
@extend .message.info;
|
|
// ^^^^^^^^^^^^^
|
|
// Error: Write @extend .message, .info instead.
|
|
|
|
@extend .main .info;
|
|
// ^^^^^^^^^^^
|
|
// Error: write @extend .info instead.
|
|
}
|
|
===
|
|
.alert
|
|
@extend .message.info
|
|
// ^^^^^^^^^^^^^
|
|
// Error: Write @extend .message, .info instead.
|
|
|
|
@extend .main .info
|
|
// ^^^^^^^^^^^
|
|
// Error: write @extend .info instead.
|
|
{% endcodeExample %}
|
|
|
|
{% markdown %}
|
|
### HTML Heuristics
|
|
|
|
When `@extend` [interleaves complex selectors][], it doesn't generate all
|
|
possible combinations of ancestor selectors. Many of the selectors it could
|
|
generate are unlikely to actually match real HTML, and generating them all
|
|
would make stylesheets way too big for very little real value. Instead, it
|
|
uses a [heuristic][]: it assumes that each selector's ancestors will be
|
|
self-contained, without being interleaved with any other selector's ancestors.
|
|
|
|
[interleaves complex selectors]: #how-it-works
|
|
[heuristic]: https://en.wikipedia.org/wiki/Heuristic
|
|
{% endmarkdown %}
|
|
|
|
{% codeExample 'html-heuristics' %}
|
|
header .warning li {
|
|
font-weight: bold;
|
|
}
|
|
|
|
aside .notice dd {
|
|
// Sass doesn't generate CSS to match the <dd> in
|
|
//
|
|
// <header>
|
|
// <aside>
|
|
// <div class="warning">
|
|
// <div class="notice">
|
|
// <dd>...</dd>
|
|
// </div>
|
|
// </div>
|
|
// </aside>
|
|
// </header>
|
|
//
|
|
// because matching all elements like that would require us to generate nine
|
|
// new selectors instead of just two.
|
|
@extend li;
|
|
}
|
|
===
|
|
header .warning li
|
|
font-weight: bold
|
|
|
|
|
|
aside .notice dd
|
|
// Sass doesn't generate CSS to match the <dd> in
|
|
//
|
|
// <header>
|
|
// <aside>
|
|
// <div class="warning">
|
|
// <div class="notice">
|
|
// <dd>...</dd>
|
|
// </div>
|
|
// </div>
|
|
// </aside>
|
|
// </header>
|
|
//
|
|
// because matching all elements like that would require us to generate nine
|
|
// new selectors instead of just two.
|
|
@extend li
|
|
{% endcodeExample %}
|
|
|
|
{% markdown %}
|
|
### Extend in `@media`
|
|
|
|
While `@extend` is allowed within [`@media` and other CSS at-rules][], it's
|
|
not allowed to extend selectors that appear outside its at-rule. This is
|
|
because the extending selector only applies within the given media context,
|
|
and there's no way to make sure that restriction is preserved in the generated
|
|
selector without duplicating the entire style rule.
|
|
|
|
[`@media` and other CSS at-rules]: /documentation/at-rules/css
|
|
{% endmarkdown %}
|
|
|
|
{% codeExample 'extend-media', false %}
|
|
@media screen and (max-width: 600px) {
|
|
.error--serious {
|
|
@extend .error;
|
|
// ^^^^^^
|
|
// Error: ".error" was extended in @media, but used outside it.
|
|
}
|
|
}
|
|
|
|
.error {
|
|
border: 1px #f00;
|
|
background-color: #fdd;
|
|
}
|
|
===
|
|
@media screen and (max-width: 600px)
|
|
.error--serious
|
|
@extend .error
|
|
// ^^^^^^
|
|
// Error: ".error" was extended in @media, but used outside it.
|
|
|
|
|
|
|
|
.error
|
|
border: 1px #f00
|
|
background-color: #fdd
|
|
{% endcodeExample %}
|