Wednesday 10 February 2016

Sass: The @extend Directive - Vanseo Design

Sass: The @extend Directive - Vanseo Design


Sass: The @extend Directive

Posted: 09 Feb 2016 05:30 AM PST

Have you ever wanted one selector to inherit the styles of another? Maybe you have a .button class for standard buttons across the site and now you want to create a larger button that should be styled the same as the rest of your buttons, except it should be larger.

An Electric Power Strip

Wouldn't it be nice if you could tell the new class to style itself like standard buttons, but bigger. The @extend directive allows you do just that. It lets one selector inherit the styles of another selector, which you can then build on.

For the last few weeks I've been looking at @-rules and directives in Sass. I talked about @-rules in general and then more specifically about @media and @import. Last week I offered some ideas for structuring your Sass directories.

Today and next week I want to talk about the @extend directive, which as I mentioned allows one selector to inherit the styles of another and allowing you write DRYer code.

The @extend Directive

Let's think for a moment how you might style a standard .button class and then a .button-large class for a larger version of the standard button. A common approach is to create a .button class with the styles you want for your buttons and then add a new .button-large class that overrides the size.

1  2  3  4  5  6  7  
.button {     styles here;  }    .button-large {     styles to make .button larger;  }

You would add both classes to any elements that should become a large button.

1  
<a class="button button-large" href="">Click Me</a>

The downside is you now have an additional class to maintain in your stylesheet and to remember to include in your HTML.

The @extend directive provides another option that doesn't require a second class on your HTML. The directive allows you to extend one selector and tell Sass that the second selector should inherit the styles of the first.

Here's how you might style a standard button and then a large button using @extend. The size of the button is controlled by its font-size. If the font-size increases, the padding will also increase and both will lead to a larger button.

1  2  3  4  5  6  7  8  9  10  11  12  
.button {      background: #900;      font-size: 1em;      padding: 0.5em 1.0em;      text-decoration: none;      color: #fff;    }    .button-large {      @extend .button;      font-size: 1.5em;    }

The .button class sets some general styles for how buttons across the site should look. The .button-large class extends the .button class through the @extend directive. It then overrides the font-size and makes it 50% larger.

The Sass compiles to the following CSS.

1  2  3  4  5  6  7  8  9  10  11  
.button, .button-large {      background: #900;      font-size: 1em;      padding: 0.5em 1.0em;      text-decoration: none;      color: #fff;    }    .button-large {      font-size: 1.5em;    }

Notice that the styles aren't repeated in the .button-large class, but rather the selector has been added to the .button selector styles keeping the CSS DRY.

Creating a large button now requires a single class instead of two. Here the first line of HTML creates a standard button and the second line creates a large button. Each link requires a single class.

1  2  
<a class="button" href="">Click Me, I'm a button.</a>    <a class="button-large" href="">Click Me, I'm a large button.</a>

The selector that @extends and inherits styles will be inserted in DRY fashion wherever the extended selector exists in the stylesheet. For example imagine your buttons come in different colors and you defined those colors on a selector that requires the element to have both classes.

1  
<a class="button green" href="">Click Me</a>

Your CSS only adds the background color when both classes are present.

1  2  3  
.button.green {     background: #090;    }

You then extend the .button class inside a .button-large class as before.

1  2  3  
.button-large {     @extend .button;    }

A .button-large selector will be inserted wherever a .button selector exists so you'll find something like this in your compiled CSS file.

1  2  3  
.button.green, .button-large.green {     background: #090;    }

When selectors are merged this way, the @extend directive can avoid unnecessary duplication. For example if your compiled code would become:

1  2  3  
.button.button-large, .button-large.button-large {     styles here;    }

The duplicate would be removed.

1  2  3  
.button.button-large, .button-large {     styles here;    }

Extending Complex Selectors

You can extend more than classes. Any selector representing a single element can be extended. Imagine your links change background color and you want to do the same when hovering over a button.

1  2  3  4  5  6  7  
a:hover {      background: #c00;    }    .button-hover {      @extend a:hover;    }

Here I created a class called .button-hover that extends the :hover pseudo class and compiles to:

1  2  3  
a:hover, .button-hover {      background: #c00;    }

As a selector, a:hover is still relatively simple. You can extend more complex selectors such as .container main .button.

Multiple @extend Directives

A selector isn't limited to extending one other selector. Selectors can use multiple @extend directives and inherit the styles of several different selectors.

For example imagine you created classes for different background colors and all your large buttons will have a green background. Not exactly something you'd do in practice, but go with it for the example.

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  
.button {      background: #900;      font-size: 1em;      padding: 0.5em 1.0em;      text-decoration: none;      color: #fff;    }    .background-green {      background: #090;    }    .button-large {      @extend .button;      @extend .green;      font-size: 1.5em;    }

The .button-large class extends both .button and .green and then bumps up the font-size. The code compiles to:

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  
.button, .button-large {      background: #900;      font-size: 1em;      padding: 0.5em 1.0em;      text-decoration: none;      color: #fff;    }    .background-green, .button-large {      background: #090;    }    .button-large {      font-size: 1.5em;    }

Another way to write multiple extends is by using a comma-separated list of selectors. In the previous example both extends could have been written as one.

1  
@extend .button, .green;

Either works. I prefer new lines because it's easier for me to read, though may find the single line easier to read.

Chaining @extend Directives

You can chain selectors so that one selector is extended by another selector, which is then extended by a third selector.

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  
a {     text-decoration: none;    }    .button {     @extend a;     background: #900;     font-size: 1em;     padding: 0.5em 1.0em;     color: #fff;    }    .button-large {     @extend .button;     font-size: 1.5em;    }

Here .button extends a link and is then extended by .button-large. The Sass compiles to:

1  2  3  4  5  6  7  8  9  10  11  12  13  14  
a, .button, .button-large {     text-decoration: none;    }    .button, .button-large {     background: #900;     font-size: 1em;     padding: 0.5em 1.0em;     color: #fff;    }    .button-large {     font-size: 1.5em;    }

Selector Sequences

At the moment you can't extend a sequence of selectors. For example the following doesn't work.

1  2  3  4  5  6  7  
.sidebar .button {     styles here;    }    .button-large {     @extend .sidebar .button    }

However a selector sequence can extend a single selector.

1  2  3  4  5  6  7  8  
.button {     styles here;    }    .sidebar .button-large {     @extend .button     styles here;    }

This is perfectly valid and compiles to:

1  2  3  4  5  6  7  
.button, .sidebar .button-large {     styles here;    }    .sidebar .button-large {     styles here;    }

Similarly you can extend one part of a selector sequence. Here .button-large extends only the .button class part of the sequence.

1  2  3  4  5  6  7  8  
.sidebar .button {     styles here;    }    .button-large {     @extend .button;     additional styles here;    }

When the Sass is compiled every instance where you would find .button, you'll now also find a duplicate using .button-large instead.

1  2  3  4  5  6  7  
.sidebar .button, .sidebar .button-large {     styles here;    }    .button-large {     styles here;    }

Merging Selector Sequences

Extending sequences of selectors can get complex in a hurry. Consider the following example.

1  2  3  4  5  6  7  
.page .sidebar .button {     styles here;    }    .container .footer .button-large {     @extend .button    }

The second selector is extending .button. There are more ways the selectors above can be merged than you might think at first. The obvious way is to substitute the entire second selector for .button in the first selector.

1  
.page .sidebar .container .footer .button-larg

However, the following selector is also valid.

1  
.container .footer .page .sidebar .button-larg

As long as .button-large comes before .page .sidebar the selector will be valid. It turns out there are 10 possible ways to merge these selectors, though not all of them are likely to be used. Instead of adding all 10 on compile, Sass generates only those likely to be useful according to the following two rules.

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

Following this rule the previous example compiles to:

1  2  3  4  5  
.page .sidebar .button,    .page .sidebar .container .footer .button-large,    .container .footer .page .sidebar .button-large {     styles here;    }
  • When the two sequences share some selectors, those selectors will be merged together and only the differences (if any still exist) will alternate.

Here I changed the previous example and replaced .container in the second selector to .page so the two will share a selector.

1  2  3  4  5  6  7  
.page .sidebar .button {     styles here;    }    .page .footer .button-large {     @extend .button    }

The Sass compiles to the following.

1  2  3  4  5  
.page .sidebar .button,    .page .sidebar .footer .button-large,    .page .footer .sidebar .button-large {     styles here;    }

The only difference in the sequence (besides the change from .button to .button-large) is that one selector contains .sidebar and the other contains .footer. These will alternate positions as you can see in the compiled code.

Closing Thoughts

Inheriting the styles of one selector into another helps make maintenance easier by having a single source for those styles. Inheritance in general reduces duplicate code and helps make your code reusable. Sass maintains the lack of duplication by compiling to DRY CSS.

If you've ever styled one selector to look similar, though not quite the same as another, you'll appreciate being able to inherit the styles using @extend.

There's more I want to cover about the @extend directive and I'll finish next week, including what to do when you want to inherit the styles of one class, but you don't actually want to compile the code of that class.

Download a free sample from my book Design Fundamentals.

Join me as I share my creative process and journey as a writer.

This posting includes an audio/video/photo media file: Download Now