@extend

@extend

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. The most common way of handling this is to use both the more general class and the more specific class in the HTML. For example, suppose we have a design for a normal error and also for a serious error. We might write our markup like so:

<div class="error seriousError">
  Oh no! You've been hacked!
</div>

And our styles like so:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  border-width: 3px;
}

Unfortunately, this means that we have to always remember to use .error with .seriousError. This is a maintenance burden, leads to tricky bugs, and can bring non-semantic style concerns into the markup.

The @extend directive avoids these problems by telling Sass that one selector should inherit the styles of another selector. For example:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

is compiled to:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd;
}

.seriousError {
  border-width: 3px;
}

This means that all styles defined for .error are also applied to .seriousError, in addition to the styles specific to .seriousError. In effect, every element with class .seriousError also has class .error.

Other rules that use .error will work for .seriousError as well. For example, if we have special styles for errors caused by hackers:

.error.intrusion {
  background-image: url("/image/hacked.png");
}

Then <div class="seriousError intrusion"> will have the hacked.png background image as well.

How it Works

@extend works by inserting the extending selector (e.g. .seriousError) anywhere in the stylesheet that the extended selector (.e.g .error) appears. Thus the example above:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.error.intrusion {
  background-image: url("/image/hacked.png");
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

is compiled to:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd; }

.error.intrusion, .seriousError.intrusion {
  background-image: url("/image/hacked.png"); }

.seriousError {
  border-width: 3px; }

When merging selectors, @extend is smart enough to avoid unnecessary duplication, so something like .seriousError.seriousError gets translated to .seriousError. In addition, it won’t produce selectors that can’t match anything, like #main#footer.

Extending Complex Selectors

Class selectors aren’t the only things that can be extended. It’s possible to extend any selector involving only a single element, such as .special.cool, a:hover, or a.user[href^="http://"]. For example:

.hoverlink {
  @extend a:hover;
}

Just like with classes, this means that all styles defined for a:hover are also applied to .hoverlink. For example:

.hoverlink {
  @extend a:hover;
}
a:hover {
  text-decoration: underline;
}

is compiled to:

a:hover, .hoverlink {
  text-decoration: underline; }

Just like with .error.intrusion above, any rule that uses a:hover will also work for .hoverlink, even if they have other selectors as well. For example:

.hoverlink {
  @extend a:hover;
}
.comment a.user:hover {
  font-weight: bold;
}

is compiled to:

.comment a.user:hover, .comment .user.hoverlink {
  font-weight: bold; }

Multiple Extends

A single selector can extend more than one selector. This means that it inherits the styles of all the extended selectors. For example:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.attention {
  font-size: 3em;
  background-color: #ff0;
}
.seriousError {
  @extend .error;
  @extend .attention;
  border-width: 3px;
}

is compiled to:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd; }

.attention, .seriousError {
  font-size: 3em;
  background-color: #ff0; }

.seriousError {
  border-width: 3px; }

In effect, every element with class .seriousError also has class .error and class .attention. Thus, the styles defined later in the document take precedence: .seriousError has background color #ff0 rather than #fdd, since .attention is defined later than .error.

Multiple extends can also be written using a comma-separated list of selectors. For example, @extend .error, .attention is the same as @extend .error; @extend .attention.

Chaining Extends

It’s possible for one selector to extend another selector that in turn extends a third. For example:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}
.criticalError {
  @extend .seriousError;
  position: fixed;
  top: 10%;
  bottom: 10%;
  left: 10%;
  right: 10%;
}

Now everything with class .seriousError also has class .error, and everything with class .criticalError has class .seriousError and class .error. It’s compiled to:

.error, .seriousError, .criticalError {
  border: 1px #f00;
  background-color: #fdd; }

.seriousError, .criticalError {
  border-width: 3px; }

.criticalError {
  position: fixed;
  top: 10%;
  bottom: 10%;
  left: 10%;
  right: 10%; }

Selector Sequences

Selector sequences, such as .foo .bar or .foo + .bar, currently can’t be extended. However, it is possible for nested selectors themselves to use @extend. For example:

#fake-links .link {
  @extend a;
}

a {
  color: blue;
  &:hover {
    text-decoration: underline;
  }
}

is compiled to

a, #fake-links .link {
  color: blue; }
  a:hover, #fake-links .link:hover {
    text-decoration: underline; }
Merging Selector Sequences

Sometimes a selector sequence extends another selector that appears in another sequence. In this case, the two sequences need to be merged. For example:

#admin .tabbar a {
  font-weight: bold;
}
#demo .overview .fakelink {
  @extend a;
}

While it would technically be possible to generate all selectors that could possibly match either sequence, this would make the stylesheet far too large. The simple example above, for instance, would require ten selectors. Instead, Sass generates only selectors that are likely to be useful.

When the two sequences being merged have no selectors in common, then two new selectors are generated: one with the first sequence before the second, and one with the second sequence before the first. For example:

#admin .tabbar a {
  font-weight: bold;
}
#demo .overview .fakelink {
  @extend a;
}

is compiled to:

#admin .tabbar a,
#admin .tabbar #demo .overview .fakelink,
#demo .overview #admin .tabbar .fakelink {
  font-weight: bold; }

If the two sequences do share some selectors, then those selectors will be merged together and only the differences (if any still exist) will alternate. In this example, both sequences contain the id #admin, so the resulting selectors will merge those two ids:

#admin .tabbar a {
  font-weight: bold;
}
#admin .overview .fakelink {
  @extend a;
}

This is compiled to:

#admin .tabbar a,
#admin .tabbar .overview .fakelink,
#admin .overview .tabbar .fakelink {
  font-weight: bold; }

@extend-Only Selectors

Sometimes you’ll write styles for a class that you only ever want to @extend, and never want to use directly in your HTML. This is especially true when writing a Sass library, where you may provide styles for users to @extend if they need and ignore if they don’t.

If you use normal classes for this, you end up creating a lot of extra CSS when the stylesheets are generated, and run the risk of colliding with other classes that are being used in the HTML. That’s why Sass supports “placeholder selectors” (for example, %foo).

Placeholder selectors look like class and id selectors, except the # or . is replaced by %. They can be used anywhere a class or id could, and on their own they prevent rulesets from being rendered to CSS. For example:

// This ruleset won't be rendered on its own.
#context a%extreme {
  color: blue;
  font-weight: bold;
  font-size: 2em;
}

However, placeholder selectors can be extended, just like classes and ids. The extended selectors will be generated, but the base placeholder selector will not. For example:

.notice {
  @extend %extreme;
}

Is compiled to:

#context a.notice {
  color: blue;
  font-weight: bold;
  font-size: 2em; }

The !optional Flag

Normally when you extend a selector, it’s an error if that @extend doesn’t work. For example, if you write a.important {@extend .notice}, it’s an error if there are no selectors that contain .notice. It’s also an error if the only selector containing .notice is h1.notice, since h1 conflicts with a and so no new selector would be generated.

Sometimes, though, you want to allow an @extend not to produce any new selectors. To do so, just add the !optional flag after the selector. For example:

a.important {
  @extend .notice !optional;
}

@extend in Directives

There are some restrictions on the use of @extend within directives such as @media. Sass is unable to make CSS rules outside of the @media block apply to selectors inside it without creating a huge amount of stylesheet bloat by copying styles all over the place. This means that if you use @extend within @media (or other CSS directives), you may only extend selectors that appear within the same directive block.

For example, the following works fine:

@media print {
  .error {
    border: 1px #f00;
    background-color: #fdd;
  }
  .seriousError {
    @extend .error;
    border-width: 3px;
  }
}

But this is an error:

.error {
  border: 1px #f00;
  background-color: #fdd;
}

@media print {
  .seriousError {
    // INVALID EXTEND: .error is used outside of the "@media print" directive
    @extend .error;
    border-width: 3px;
  }
}

Someday we hope to have @extend supported natively in the browser, which will allow it to be used within @media and other directives.

doc_Sass
2016-11-11 13:08:59
Comments
Leave a Comment

Please login to continue.