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

  1. Reconsider not using @extend. The alternatives are better.
  2. Only extend %placeholder rules so your intention is obvious.
  3. 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