Sass: Placeholders and @extend-Only Selectors

Sass’ @extend directive allows one selector to inherit the styles of another and it does so in a way that produces DRY CSS code. When combined with placeholders you can further abstract your code so that some styles are compiled only when extended and used in another selector.

A placeholder for a newspaper image

Last week I walked you through the @extend directive and how it’s used. Today I want to continue talking about @extend, particularly in combination with placeholders. I’ll close with a few odds and ends that didn’t find their way into last week’s post.

Placeholders and @extend-Only Selectors

Imagine you have a handful of buttons to style. The buttons all share the same characteristics, except that each will be a different color.

You create a .button class to contain all the code common to your buttons and then you create additional classes for each to extend the .button class before adding a background color.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.button {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: #fff;  
}

.button-red {  
  @extend .button;  
  background: #900;  
}

.button-green {  
  @extend .button;  
  background: #090;  
}

.button-blue {  
  @extend .button;  
  background: #009;  
}

The Sass compiles to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.button, .button-red, .button-green, .button-blue {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: #fff;  
}

.button-red {  
  background: #900;  
}

.button-green {  
  background: #090;  
}

.button-blue {  
  background: #009;  
}

Since every one of your buttons likes requires a background color, you’ll probably never use the .button class directly. Instead you add one of the .button-color classes to your HTML.

That’s where placeholders come in. They can be used anywhere a class or id can and they use a percent sign (%) instead of a hash (#) or a period (.). The difference is they aren’t compiled to CSS.

Here’s the previous example with one small change. Instead of extending a .button class, I’m extending a %button placeholder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%button {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: #fff;  
}

.button-red {  
  @extend %button;  
  background: #900;  
}

.button-green {  
  @extend %button;  
  background: #090;  
}

.button-blue {  
  @extend %button;  
  background: #009;  
}

Here’s the code after it’s been compiled. There’s no selector for .button (or %button) as the placeholder isn’t included when the code is compiled to CSS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.button-red, .button-green, .button-blue {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: #fff;  
}

.button-red {  
  background: #900;  
}

.button-green {  
  background: #090;  
}

.button-blue {  
  background: #009;  
}

That might not seem like a big savings in code. Here it only saved one instance of the text .button. However, it’s useful if you’re developing a theme or library, where you might create dozens of buttons that will only have their code output if they’re being called in the theme.

You might also have .button-green and .button-blue inheriting button styles from several different selectors. Maybe one for buttons and another for background colors. You could conceivably have all sorts of selectors that only exist to get inherited by other classes, but never used directly.

Silent Classes

Placeholders act like silent classes in that they allow you to use @extend to inherit styles, but those styles won’t compile to CSS unless they’re extended and used by another selector.

By default Sass will add the extending selector alongside the selector being extended, separating both with a comma. For example here a style is applied to the .button class and an additional style is applied to any .button located in the .sidebar. A .button–2 class extends the .button class

1
2
3
4
5
6
7
8
9
10
11
.button {  
  background: green;  
}

.sidebar .button {  
  font-size: 0.875em;  
}

.button-2 {  
    @extend .button;  
}

When the Sass is compiled, everywhere you see .button, you’ll now see an additional selector that uses .button–2 in place of .button.

1
2
3
4
5
6
7
.button, .button-2 {  
  background: green;  
}

.sidebar .button, .sidebar .button-2 {  
  font-size: 0.875em;  
}

What if you wanted .button–2 to extend .button only when it’s not located inside the .sidebar? In other words you want the previous example to compile to:

1
2
3
4
5
6
7
.button, .button-2 {  
  background: green;  
}

.sidebar .button {  
  font-size: 0.875em;  
}

You can create a silent class using a placeholder and extend the placeholder instead of the selector. For example:

1
2
3
4
5
6
7
8
9
10
11
12
.button,  
%button {  
  background: green;  
}

.sidebar .button {  
  font-size: 0.875em;  
}

.button-2 {  
    @extend %button;  
}

In DRY fashion I added placeholder to the .button class and then extended the placeholder inside .button–2 instead of extending the class. Since the placeholder isn’t present in the .sidebar .button selector, that selector won’t be extended.

1
2
3
4
5
6
7
.button, .button-2 {  
  background: green;  
}

.sidebar .button {  
  font-size: 0.875em;  
}

Now both .button and .button–2 will have a green background, but only .button will have a reduced font-size if it’s inside the .sidebar.

The !optional Flag

We all make mistakes. Take the following where I’m extending a class called .nav-button without creating the .nav-button class.

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

In a case like this, Sass will throw an error and your code won’t compile. At least that’s the default. You can override that behavior with the !optional flag

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

With the !optional flag set your Sass will compile to CSS despite the error. Of course, as there is no .nav-class to extend, their are no styles for .nav-button-large to inherit, which in this case means there’s no .nav-button-large.

Using @extend inside Other Directives

You can use @extend directives inside other directives with some restrictions. For example Sass would need to create a lot of duplicate CSS so that rules created outside of a @media query can apply to selectors inside the @media query.

Because of that you can only use @extend within @media to extend selectors inside the same @media block. The restriction is the same for other directives.

This first block of code is fine, because .button-large extends a class inside the same @media directive.

1
2
3
4
5
6
7
8
9
10
@media screen {  
 .button {  
   styles here;  
 }

  .button-large {  
    @extend .button;  
    more styles here;  
  }
}

However, this block doesn’t work since .button-large is now trying to extend a class outside of the @media directive.

1
2
3
4
5
6
7
8
9
10
.button {  
 styles here;  
}

@media screen {  
 .button-large {  
   @extend .button;  
   more styles here;  
 }
}

Closing Thoughts

Hopefully you can see the benefits of the @extend directive. They’re a tool that makes use of inheritance and helps you produce DRYer CSS code with fewer classes and ids in your HTML.

When you use them in combination with placeholders you can abstract styles by create @extend-only selectors that are only used when needed making it easier to maintain themes and libraries.

There’s one last directive I’d like to discuss before wrapping up this series about @-rules and directives. Next week I want to talk about the @mixin directive, which lets you define styles for reuse throughout your stylesheet.

Download a free sample from my book, Design Fundamentals.

Leave a Reply

Your email address will not be published. Required fields are marked *