Nesting
Basic nesting
Whenever you declare a new rule inside another rule it is called nesting. With basic nesting, as shown below, the nested selector will be compiled as a new CSS selector with all its parents prepended, separated by a space.
// SCSS
.parent {
margin: 1rem;
.child {
float: left;
}
}
// CSS output
.parent {
margin: 1rem;
}
.parent .child {
float: left;
}
Nesting depth
Nesting is a very powerful feature, but should be used with caution. It can happen quite easily and quickly, that you start nesting and carry on including all children in a nest, of a nest, of a nest. Let me demonstrate:
// SCSS
header {
// [css-rules]
.holder {
// [css-rules]
.dropdown-list {
// [css-rules]
ul {
// [css-rules]
li {
margin: 1rem 0 0 1rem;
}
}
}
}
}
// CSS output of the last rule
header .holder .dropdown-list ul li {
margin: 1rem 0 0 1rem;
}
Problems
Specificity
The li
from the example above has a margin
set. Let’s say we want to override this in a media-query later on.
@media (max-width: 480) {
// will not work
.dropdown-list ul li {
margin: 0;
}
// will work
header .holder .dropdown-list ul li {
margin: 0;
}
}
So by nesting too deep consequently you’ll have to nest deep again whenever you want to overwrite a certain value. Even worse, this is often where the rule !important
comes to use.
@media (max-width: 480) {
// BIG NO-NO, don't do this
.dropdown-list ul li {
margin: 0 !important;
}
Why is the !important
-rule is a bad idea
You should write your SCSS in a good fashion that these workarounds aren’t even necessary in the first place. Using !important
on such a minor issue already will lead you down a rabbit hole!
Reusability
This is fairly similar to the specificity problem, but worth pointing out separately. If you style something like a button or even a dropdown, you might want to reuse those styles somewhere else on your page.
By nesting too deeply your styles are only bound to the elements sitting inside the most outer parent (the element at the top of your SCSS). This leads you to copy styles and paste them somewhere else again. Possibly in an other nested rule.
Your stylesheets will become larger and larger and more difficult to maintain.
How deep should you nest?
Most styleguides set the maximum depth to 2. This is good advice in general, as there are only very few occasions where you’d want to nest deeper. Most of the time, 2 is enough.
Nesting with @at-root
Nesting is probably most often used to create more specific selectors, but it can also be used simply for code organization. Using the @at-root
directive, you can ‘jump out’ of where you nest it in your Sass, bringing you back at the top level. Doing this allows you to keep styles grouped without creating more specificity than you need.
For example, you could to something like this :
.item {
color: #333;
@at-root {
.item-wrapper {
color: #666;
img {
width: 100%;
}
}
}
.item-child {
background-color: #555;
}
}
That would compile to this :
.item {
color: #333;
}
.item-wrapper {
color: #666;
}
.item-wrapper img {
width: 100%;
}
.item .item-child {
background-color: #555;
}
By doing this, all of our styles related to the .item
class are together in the SCSS, but we don’t necessarily need that class in every selector.
Excluding contexts
By default declarations inside @at-root
will appear in any context. This means that rules inside a @media
block for instance will remain there.
@media print {
.item-wrapper {
@at-root {
.item {
background: white;
}
}
}
}
// Will compile to
@media print {
.item {
background: white;
}
}
This is not always desired behavior, so you can exclude the media context, by passing media
to the the without
option of the @at-root
directive.
@at-root (without: media) {..
For more information, see the official documentation
The parent selector (&)
Nesting is great for keeping related selectors together to make it easier for future developers to understand your code. The parent selector, represented by an ampersand (”&”) can help do that in more complex situations. There are several ways its can be used.
Create a new selector that requires both the parent selector and another on the same element by placing the new selector directly after a parent selector.
// SCSS
.parent {
&.skin {
background: pink;
}
}
// CSS output
.parent.skin {
background: pink;
}
Have the parent appear after a nested selector in the compiled CSS by placing the parent selector after the nested selector.
// SCSS
.parent {
.wrapper & {
border: 1px solid black;
}
}
// CSS output
.wrapper .parent {
border: 1px solid black;
}
States and pseudo-elements
Besides using nesting for classes and children, nesting with the parent selector is also commonly used to combine the states of :active
, :hover
and :focus
for links.
// SCSS
a {
color: blue;
&:active,
&:focus {
color: red;
}
&:visited {
color: purple;
}
}
// CSS output
a {
color: blue;
}
a:active,
a:focus {
color: red;
}
a:visited {
color: purple;
}
Similarly, you can style pseudo-elements by nesting with the parent selector.
// SCSS
.parent {
&::after {
display: table;
clear: both;
content: '';
}
&::only-child {
font-weight: bold;
}
}
// CSS output
.parent::after {
display: table;
clear: both;
content: '';
}
.parent::only-child {
font-weight: bold;
}
Nesting properties
Some CSS properties belong to a namespace, for instance border-right
belongs to the border
namespace. To write less, we can utilize property nesting, and skip these prefixes, even on multiple levels.
If we needed to create a border on the right and left of a class named .borders
we could write this:
//SCSS
.borders {
border: 2px dashed blue;
border: {
left: 1px solid black;
right: 1px solid red;
}
}
// CSS output
.borders {
border: 2px dashed blue;
border-left: 1px solid black;
border-right: 1px solid red;
}
This saves us having to write border-right
and border-left
, however we are still writing repetitive code with the lines 1px solid black
and 1px solid red
. We can write still less repetitive CSS with the folowing:
// SCSS
.borders {
border: 2px dashed blue {
left: 1px solid black;
right: {
color: red;
}
}
}
// CSS output
.borders {
border: 2px dashed blue;
border-left: 1px solid black;
border-right-color: red;
}