Post date:
This blogpost is based on a short talk I gave at the first Slovenia CSS user group meetup in February 2015 and originally written for the Origammi company blog. It's a collection of random techniques that can be used with the latest version of Sass to make styling cleaner and lighter.
While writing Sass code, placeholder selectors and mixins appear to be quite similar. You declare them, include a bit of styling and then either include the mixin or extend the placeholder selector when styling an element. The end result is that the styling from the mixin/placeholder selector will be applied to the element in question.
What may not be so obvious in this process is the fact that the produced CSS is actually different in both cases.
If we take the following Sass code ...
=foo
color: red
.foo-1
+foo
.foo-2
+foo
%bar
color: red
.bar-1
@extend %bar
.bar-2
@extend %bar
... the produced CSS will look like this:
.foo-1 {
color: red; }
.foo-2 {
color: red; }
.bar-1, .bar-2 {
color: green; }
As you can see, it is much better to use placeholder selectors when we are just trying to reuse a chunk of styling, as the selectors will be smartly combined instead of the properties being duplicated.
The first example might be a bit contrived, since mixins are usually used with parameters, which placeholder selectors don't support. Let's have a look at an example of a button mixin:
=button($background-color, $font-color)
color: $font-color
background-color: $background-color
border-radius: 5px
line-height: 10px
.button-brown
+button(saddlebrown, white)
.button-green
+button(aquamarine, black)
This produces the following CSS:
.button-brown {
color: white;
background-color: saddlebrown;
border-radius: 5px;
line-height: 10px; }
.button-green {
color: black;
background-color: aquamarine;
border-radius: 5px;
line-height: 10px; }
While this is good enough, it can still be optimised further. We can extract any properties that don't depend on parameters to a placeholder selector and have the mixin extend it:
%button-common
border-radius: 5px
line-height: 10px
=button($background-color, $font-color)
@extend %button-common
color: $font-color
background-color: $background-color
.button-brown
+button(saddlebrown, white)
.button-green
+button(aquamarine, black)
This way, only the properties we pass as parameters are (unavoidably) duplicated, while the common ones are applied in a cleaner way:
.button-brown, .button-green {
border-radius: 5px;
line-height: 10px; }
.button-brown {
color: white;
background-color: saddlebrown; }
.button-green {
color: black;
background-color: aquamarine; }
A very useful feature in Sass is the &
symbol, which allows us to access the parent selector in a nested context. If you use Sass, there's a good chance you're already using it for simpler purposes such as the following ...
.foo
&.bar
background: blue
&:hover
background: red
&:after
content: ''
... which produces:
.foo.bar {
background: blue; }
.foo:hover {
background: red; }
.foo:after {
content: ""; }
But there are many less known uses for it and they can all be extremely useful if you like to keep as much styling nested under the same selector as possible.
If you want to apply some styles to the current selector when it appears in some specific external context, e.g. when there is a specific body class applied, you can do the following:
.foo
.body-class &
background: pink
.body-class .foo {
background: pink; }
You can't specify an additional element selector (e.g. h1&
) in the context of .foo
to get a combination such as h1.foo
, but it's still doable with just a tiny bit more code.
.foo
@at-root #{selector-unify(&, h1)}
background: orange
h1.foo {
background: orange; }
The adjacent sibling selector trick is useful in cases where you want to apply some styling to all sibling elements except the first one. It's an alternative to applying it to all elements and then overriding it for the first one and it's really easy to do with the &
parent selector.
.foo
& + &
margin-left: 20px
.foo + .foo {
margin-left: 20px; }
If you want to break your styling down into a bunch of reusable classes that can be applied in combinations (kind of what Bootstrap and similar frameworks do with their components), you can do this in a very clean way and keep all classes for one component nested under the same selector.
.button
font-size: 10px
line-height: 20px
&-warning
background: red
color: white
&-success
background: green
color: black
.button {
font-size: 10px;
line-height: 20px; }
.button-warning {
background: red;
color: white; }
.button-success {
background: green;
color: black; }
Since version 3.2, Sass supports the extremely useful @content directive (I briefly mentioned it in an older post), which allows you to specify properties under a mixin call and use the mixin to write a nesting context. The following example demonstrates how this can be used to apply IE6-specific styling using a hack or apply media queries transparently.
=ie6-only
* html &
@content
.foo
+ie6-only
background-image: url(/logo.gif)
=desktop
@media only screen and (min-width: 769px)
@content
.foo
background: blue
+desktop
background: red
* html .foo {
background-image: url(/logo.gif); }
.foo {
background: blue; }
@media only screen and (min-width: 769px) {
.foo {
background: red; } }
Sass maps are often used to extract configuration values (font sizes, colours, breakpoints etc.) from the actual styling. Sometimes these configuration values are also required in your JavaScript code. An example of this might be when you define breakpoint sizes in Sass and want to do some media query based scripting.
For these occasions, you can use Sass to generate JSON objects and store them in the content
property (it can also be font-size
or anything else that allows random strings) of a hidden pseudoelement. You can then access those elements with JS, get the value from the property, parse the JSON and voila - your configuration is accessible in JavaScript.
$breakpoints: ('mobile' : '(max-width: 768px)', 'desktop': '(min-width: 769px)')
$breakpoint-json: null
@each $name, $value in $breakpoints
$breakpoint-json: append($breakpoint-json,
"\"#{$name}\":\"#{map-get($breakpoints, $name)}\"", comma)
$breakpoint-json: "{#{$breakpoint-json}}"
body::before
content: $breakpoint-json
display: none
body::before {
content: '{"mobile":"(max-width: 768px)", "desktop":"(min-width: 769px)"}';
display: none; }
These are just a few examples of what can be done with different features of Sass, you can find a bunch of similar tricks and use cases on The Sass Way blog. If you're only using Sass for the nesting, you're missing out on a lot of cool stuff, so check out the examples and start using it in real projects - it's really worth it.