Extending Placeholder Selectors with Sass

blog_css

I recently wrote an article on creating a themable button set with Sass where I used a mixin to define all base styles, then a color value was passed as an argument when included in each theme.

Example:

@mixin btn-theme($btn-color) { ... }

// Themes

.purple {
   @include btn-theme($purple);
}

The concept of mixins seems simple on the surface, but if we’re not careful, things can quickly get out of hand once our Sass starts to grow because they duplicate a lot of CSS. It’s essentially copying and pasting blocks of code.

Fortunately, in our case there’s really no duplicate code to be concerned about. Using a mixin for creating the themes is still an efficient approach––it generates a different output with every use because it’s dependent on the color argument.

View the full example in Codepen.

Things Can Be Better

I have other concerns about this approach:

  • Every button element relies on two HTML classes: the general base style class and the more specific theme class.
  • The theme classes are purely presentational.
  • A number of unused CSS rules are being outputted.
<button class="purple btn">Start!</button>

So there is plenty of room for improvement, especially by taking advantage of two handy Sass features: @extend and placeholder selectors.

The Sass @extend Feature

The @extend directive allows one selector to inherit the styles of another selector. It prevents code bloat by grouping selectors that share the same styles into one rule.

Here’s a basic example:

.square {
   width: 200px;
   height: 200px;
   background-color: lightcoral;
}
.circle {
   @extend .square;
   border-radius: 50%;
}

Will compile to:

.square, .circle {
  width: 200px;
  height: 200px;
  background-color: lightcoral;
}
.circle {
  border-radius: 50%;
}

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

.ellipse {
   @extend .circle;
   height: 120px;
}

Compiles to:

.square, .circle, .ellipse {
  width: 200px;
  height: 200px;
  background-color: lightcoral;
}
.circle, .ellipse {
  border-radius: 50%;
}
.ellipse {
  height: 120px;
}

To break it down simply: one class can have all the styles of another class, as well as its own specific styles. View this in CodePen.

Putting @extend to Work

@extend can benefit us because our buttons can inherit everything in the .btn style block without having to explicitly state it in the markup.

In our btn-theme mixin, let’s extend the .btn class:

@mixin btn-theme($btn-color) {
   ... 
   @extend .btn;
}

Now every .btn class can be removed from the markup because the theme class will always inherit its styles.

// Themes

.purple {
   @include btn-theme($purple);  
   /* btn-theme mixin now inherits .btn styles */
}
...

But we’re still left with the presentational classes in the markup––they don’t mean anything but arbitrary colors at this point.

<button class="purple">
   Button
</button>
<button class="blue">
   Button
</button>

The New “Silent Class”

Sass 3.2 introduced a special feature called placeholder selectors. They’re also referred to as “silent classes” because they won’t appear in our CSS output unless we @extend them.

It’s usually better to extend a placeholder selector instead of a class selector, so let’s make our .btn class a silent class. This way it won’t “exist” in our CSS output until we’re ready to use it.

A silent class is defined by using a % in front of the selector name:

%btn  {
   ...
}

We’ll also need to add the % when extended:

@mixin btn-theme($btn-color) {
   ... 
   @extend %btn;
}

This is an extremely useful feature because we can now create an entire library of theme colors, but they won’t get created (or outputted) until we call them.

Creating a Theme Library

Let’s create a new partial in the “scss” folder called “_themes.scss,” which we’ll need to import into “style.scss.”

@import 'themes';

In “_themes.scss” we can now create placeholder selectors of various color options––as many as we want. Check out the ones I created.

Examples:

%darkorange {
   @include btn-theme(#FF8C00);	
}
%darkslategray {
   @include btn-theme(#2F4F4F);	
}

Now we can give our HTML classes more meaningful names like:

<button class="btn-delete">
   Delete
</button>

<input type="submit" class="btn-submit">

If we need to change a color theme, we can reference one from our themes library by simply changing the extended silent class in _buttons.scss.

.btn-delete {
	@extend %firebrick;
}
.btn-submit {
	@extend %tomato;
}

And because they are silent classes, if any are reused, they will be properly extended in the CSS.

Conclusion

This is a more viable approach because we’ve created our solution entirely in the CSS and not the markup. I created a GitHub repo containing all the example code we discussed –– take a look.

Have you used @extend or placeholder selectors in your project? Let us know in the comments below.

Free Workshops

Watch one of our expert, full-length teaching videos. Choose from either HTML, CSS or Wordpress.

Start learning

Guil Hernandez

Guil is the front-end design teacher at Treehouse. You can follow Guil on Twitter at @guilh.

Comments

8 comments on “Extending Placeholder Selectors with Sass

  1. Just am starting out in the SASS world, but this was good to see as an example going forward. Thanks!

  2. Thanks for the helpful tutorial. Is SASS much easier to learn than CSS? Are there video tutorials on treehouse that I can watch. I just completed the css foundations portion in the members area and would like to know more about SASS or even LESS. Thanks in advance.

    • You will need to learn CSS in order to work with SASS. SASS is essentially working with what you already learned in the CSS foundations course. With SASS you can go as in-depth with mixins, @extend directives or simply just use nested styles.

  3. Is it bad practice to use a placeholder selector within a mixin. For e.g.

    @mixin boxes($w, $dir, $n: 10) {

    %base {
    float: $dir;
    }

    @for $i from 1 through $n {
    .box-#{$i} {
    @extend %base;
    width: $i * $w;
    }
    }

    }

    @include boxes(5px, left);

    I don’t see this example mixin being reused more than once to setup the boxes. But let’s say there was an example where the mixin would be called more than once. Then, I’m seeing that the base placeholder selector would be defined, so to speak, more than once. In my tests of this I’m not getting any errors. Thoughts?

    Also, base must occur within the mixin’s definition since it depends on one of its parameters.

  4. Great article! I have just recently started using the silent classes in Sass and it has helped reduce my CSS file size and reduce my HTML markup as well, especially since I have created a whole class library for my own grid based framework.

  5. Great stuff Guil – much more efficient using %silent classes. My one gripe is that you cant @extend across files with SASS.