by Dale Sande
I have been promoting the use of Sass' @extend
function for some time now so it is safe to say that I have been very cautious of how this feature processes Sass into CSS. I still stand behind this feature and it is extremely powerful, but with power comes responsibility.
In a recent project I needed to apply some basic styling to some heading tags, we have all seen something like the following.
h1 {
@include heading();
}
h2 {
@include text($heading_2);
}
h3 {
@include text($heading_3);
}
Pretty textbook stuff. Each line of code there is applying a specific style to the the selector. We would expect the following CSS output from our Sass.
CSS
h1 {
font-size: 3.83333em;
line-height: 1.17391em;
margin-bottom: 0.3913em;
color: #333333;
font-weight: normal;
font-family: "Helvetica Neue", Arial, sans-serif;
text-transform: uppercase; }
h2 {
font-size: 2.66667em;
line-height: 1.125em;
margin-bottom: 0.5625em; }
h3 {
font-size: 2.33333em;
line-height: 1.28571em;
margin-bottom: 0.64286em; }
Then things get a little more interesting. For the h1
you want to do some text transformation, so you apply text-transform: uppercase;
decloration to the h1
like so.
h1 {
@include heading();
text-transform: uppercase;
}
What about h2
and h3
? We could either specifically add the text-transform: uppercase;
to each selector, but we want to use Sass' @extend
feature to extend the CSS rules from the h1
as well, so we update the code as shown in the following example.
h1 {
@include heading();
text-transform: uppercase;
}
h2 {
@include text($heading_2);
@extend h1;
}
h3 {
@include text($heading_3);
@extend h1;
}
This produces the following CSS as expected. Notice where we have h1, h2, h3
properly extended.
CSS
h1, h2, h3 {
font-size: 3.83333em;
line-height: 1.17391em;
margin-bottom: 0.3913em;
color: #333333;
font-weight: normal;
font-family: "Helvetica Neue", Arial, sans-serif;
text-transform: uppercase; }
h2 {
font-size: 2.66667em;
line-height: 1.125em;
margin-bottom: 0.5625em; }
h3 {
font-size: 2.33333em;
line-height: 1.28571em;
margin-bottom: 0.64286em; }
At this point we are feeling pretty awesome and this is all working as expected. As we begin to build a module for our UI, there comes a point where we need to make some color modifications to the heading styles.
In our example, headings are inheriting it's color from the <html>
tag which is #333
. The new module has a transparent background-color
based off of the #333
color, something like background-color: transparentize($color, 0.5);
or background-color: rgba(51, 51, 51, 0.5);
for the CSS kids in the room.
Design solution - make the text white. Starting with the h1
we would write the following code.
.module {
background-color: transparentize($color, 0.5);
h1 {
color: $white;
}
}
Perfect, refresh the browser. Wait, we notice something unexpected. The h1
, h2
and h3
are all white? What? But we only specified the h1
, why did this happen? Opening the inspector we see that .module h1, .module h2, .module h3
have all been extended and the declaration of color: white;
applied as seen in the following example.
CSS
.module {
background-color: rgba(51, 51, 51, 0.5); }
.module h1, .module h2, .module h3 {
color: white; }
Looking at the Sass docs we see the following statement, @extend works by inserting the extending selector (e.g. .seriousError) anywhere in the stylesheet that the extended selector (.e.g .error) appears. What does that really mean?
Using the examples in the Sass docs, let's do something like the following. In the same way that we used heading selectors to create a primary selector that is extended into the secondary selector, this example uses simple CSS classes.
.error {
color: error;
}
.seriousError {
@extend .error;
}
To mix things up we crate a new .new-block
selector and nest inside a new named-spaced CSS rule for .error
.
.new-block {
.error {
color: nested-error;
}
}
And yup, as expected, not only was .error
extended with .seriousError
, but the nested version of .error
was extended as well.
CSS
.error, .seriousError {
color: error; }
.new-block .error, .new-block .seriousError {
color: nested-error; }
I am reminded by the following once again
@extend works by inserting the extending selector (e.g. .seriousError) anywhere in the stylesheet that the extended selector (.e.g .error) appears.
Having run this concept through multiple scenarios in SassMeister I continue to come to the same simple conclusion. Be aware of the loop. It's really that simple.
Taking the previous heading example, if we were to of used an h2
nested within the .module
class, none of this would have happened. The h2
is extending h1
, but nothing is yet extending h2
, so the loop is short and you get what you would expect in the following example.
.module h2 {
color: white;
}
Our h1
is extended by the h2
and the h3
. As a result any re-introduction of a new h1
style rule will result in the full cascade of extended selectors.
Just doing this …
h1 {
foo: bar;
}
Will give you this …
h1, h2, h3 {
foo: bar;
}
And if you were to do something like this …
.super {
.cali {
.fragi {
.listic {
h1 {
border-width: 1px;
}
}
}
}
}
Yup, you'd get this …
.super .cali .fragi .listic h1, .super .cali .fragi .listic h2, .super .cali .fragi .listic h3 {
border-width: 1px;
}
Play with the SassMeister Living Gist