Extends
GravDept uses CSS processors. This enables the @extend
directive, which isn’t in the CSS spec.
The @extend
directive allows a selector to inherit the properties from another. It does this by rewriting your CSS to group each extended selector into one rule.
Having said that — never use @extend
. It causes multiple problems and it’s rarely the best approach.
How to avoid @extend
1. Use CSS inheritance
CSS was designed for inheritance. Abstract your patterns into more reusable base patterns. If that’s too complicated…
2. Isolate the patterns
Don’t abstract or inherit anything. Separate the patterns completely, and use variables to keep styling consistent. If there’s too much overlap for comfort…
Pause — see #1 again. That’s probably the solution.
3. Use mixins
Mixins are the best way to apply the same properties in different patterns or contexts. For example, changing the appearance of multiple elements across breakpoints.
When @extend
is useful
Using @extend
helps you avoid writing multiple class names on HTML elements. This solves a problem we don’t have.
GravDept prefers to write maintainable HTML rather than avoid doing so. Our naming standards encourage using multiple classes on HTML elements. We always write HTML deliberately, so being unable or unwilling to modify HTML is a rare circumstance.
Why @extend
is harmful
1. @extend
is too DRY
@extend
makes CSS output totally DRY, which isn’t important, and that causes all the following problems to do this.
If you manually type a declaration 50 times in a project, you are repeating yourself: this is not DRY. If you can generate that declaration 50 times without having to manually repeat it, this is DRY: you are generating repetition without actually repeating yourself. This is quite a subtle but important distinction to be aware of. Repetition in a compiled system is not a bad thing: repetition in source is a bad thing. Harry Roberts, When to use @extend; when to use @mixin
2. @extend
is invisible
When a selector is extended, it’s impossible to know that — unless the code is nearby. Eventually you’ll break something by:
- Changing the selector being extended.
- Changing the properties being inherited so they conflict with the extenders.
.thing-aaa {}
// ...hundreds of lines later...
.thing-zzz {
@extend .thing-aaa;
}
3. @extend
rewrites your CSS
@extend
creates grouped selectors that don’t exist in your source code. You can’t search for them.
4. @extend
affects inheritance via precedence
When @extend
regroups rules each selector’s specificity stays the same. But source order changes, so selector precedence can break.
Your tooling should never force you to raise specificity to override its output.
5. @extend
weakens sourcemaps
Sourcemaps are less helpful because extended rules don’t identify multiple origins.
6. @extend
increases file size
When the selector using @extend
is longer than the properties it avoids repeating, your uncompressed file size will increase.
7. @extend
weakens gzip
compression
CSS should be served with gzip
compression, so each time a string is repeated compression improves.
@extend
makes properties appear once, and selectors appear many times (in groups depending on what extends what). gzip
compresses each unique grouped selector poorly.
@mixin
makes properties appear many times, and selectors appear once. gzip
compresses each set of repeated properties well.
8. @extend
is not extensible
@extend
cannot override properties of the rule it extends, which makes it significantly less useful.
@mixin
has extensibility features:
- Accept arguments.
- Define default values for arguments.
- Define variable arguments (lists per argument).
- Allow anything to be passed through
@content
.
9. @extend
fails in media queries
.thing-one {}
@media min-width: 600px {
.thing-two {
@extend .thing-one; // This will fail.
}
}
You’re asking @extend
to produce this invalid CSS output:
.thing-one,
@media min-width: 600px { .thing-two } {
// Declarations
}
Processing fails because you can’t group selectors with and without media queries.
- You may not @extend an outer selector from within @media.
- You may only @extend selectors within the same directive.
@mixin
works inside media queries.
10. @extend
may surpass the 4095 rule max
Internet Explorer 6–9 are limited to maximum 4095 rules per stylesheet. Additional rules are ignored.
@extend
can rapidly increase the number of rules because it favors repeating selectors instead of properties. We don’t support IE 6–9 today, but if your stylesheet has 4000+ rules that’s a signal your CSS stinks. A well-written, complex eCommerce site probably needs ~2000 rules.
11. @extend
is unpredictable
@extend
will extend every instance of the selector it matches. This example comes straight from Harry Roberts.
.foo {
color: red;
}
.footer .foo {
font-weight: bold;
}
.bar {
@extend .foo;
}
You might expect this CSS output:
.foo,
.bar {
color: red;
}
.footer .foo {
font-weight: bold;
}
But you actually get this:
.foo,
.bar {
color: red;
}
.footer .foo,
.footer .bar {
font-weight: bold;
}
You probably didn’t want the .footer .bar
selector, but your CSS has one. This gets weirder the more complicated your @extend
usage is. You won’t notice unless you inspect the output carefully.
How to use @extend
responsibly
- Reconsider not using
@extend
. The alternatives are better. - Only extend
%placeholder
rules so your intention is obvious. - Check your CSS output carefully. Whoa, what are those bite marks from?
Example with @extend
and %placeholder
You were you listening, right? Don’t use @extend
. This is just an example.
This simple example shows the complication in your source and output CSS just to avoid writing an extra HTML class.
Source SCSS
%message-base {
padding: 10px;
background: #CCC;
color: #333;
}
.message {
@extend %message-base;
}
.success-message {
@extend %message-base;
color: green;
}
.error-message {
@extend %message-base;
color: red;
}
Output CSS
.message,
.success-message,
.error-message {
padding: 10px;
color: #CCC;
color: #333;
}
.success-message {
color: green;
}
.error-message {
color: red;
}
HTML usage
<div class="message">Plain message.</div>
<div class="success-message">Success message.</div>
<div class="error-message">Error message.</div>
Example with plain CSS and smarter class naming
- Source and output CSS is identical. No magic. Super obvious.
- BEM-like naming convention implies relationships.
- HTML is more verbose, but very readable.
Plain old CSS
.message {
padding: 10px;
background: #CCC;
color: #333;
}
.message--success {
color: green;
}
.message--error {
color: red;
}
HTML usage
<div class="message">Plain message.</div>
<div class="message message--success">Success message.</div>
<div class="message message--error">Error message.</div>
Resources
- Sass Documentation — @extend
- Sass Documentation — %placholder
- Harry Roberts — Extending silent classes in Sass on 2014-01-07
- Harry Roberts — When to use @extend; when to use @mixin on 2014-11-20
- Hugo Giraudel — Why You Should Avoid Sass @extend on 2014-12-11
- Belly Card — Sass Mixins vs Extends: The Data on 2015-03-09