Here are the two HTML examples. The difference is the additional bold
class.
<!-- example 1 -->
<div class="parent">
<div class="child"><span>Hello World<span></div>
</div>
<!-- example 2 -->
<div class="parent bold">
<div class="child"><span>Hello World<span></div>
</div>
How to apply styles to our Hello World only when a bold
class is present?
Add a separate CSS rule
.bold span {
font-weight: bold;
}
/* or more specific */
.bold.parent .child span {
font-weight: bold;
}
Although this will work, we separate element styling which will not scale well in the long term.
Encapsulate the CSS rule with the root selector
.parent {
// lines of rules
span {
}
// lines of rules
.child {
}
// lines of rules
&.bold .child span {
font-weight: bold;
}
}
This will work as well and it's better than the previous example, but still will have scaling issues if we style large component with multiple rules.
Using Sass's @at-root rule
.parent {
// some CSS rules
.child {
// some CSS rules
span {
// some CSS rules
@at-root .bold {
font-weight: bold;
}
}
}
}
This will compile to .bold .parent .child span
and won't help us.
It is also unpractical to hardcore selector with @at-root
like this:
.parent {
// some CSS rules
.child {
// some CSS rules
span {
// some CSS rules
@at-root .parent.bold .child span {
font-weight: bold;
}
}
}
}
Solution with mixins
Here is our new most-outer-selector
mixin:
@mixin most-outer-selector($new-class) {
$current-selector: &;
$new-selector: [];
@each $item in $current-selector {
$first-item: nth($item, 1);
$appended-item: $first-item + $new-class;
$new-item: set-nth($item, 1, $appended-item);
$new-selector: append($new-item, $new-selector);
}
@at-root #{$new-selector} {
@content;
}
}
.parent {
// some CSS rules
.child {
// some CSS rules
span {
// some CSS rules
@include most-outer-selector('.bold') {
font-weight: bold;
}
}
}
}
This will compile CSS selector to .parent.bold .child span
.
How it works
We use our mixin within the span element. This way we will place our styles where it should be.
To get the nested selector .parent .child span
we have to use Parent Selector.
From the SCSS documentation: When a parent selector is used in an inner selector, it’s replaced with the corresponding outer selector.
This is what the line $current-selector: &;
is doing. We fetch the whole selector.
Next we initialise an empty array to the new variable $new-selector
which will be our new selector at the end.
In the @each
block we loop through our $current-selector
. In our case, the array will have only one item. To have more items, our CSS must contain selectors with comma like .child, .child2
.
Inside the loop, the $item
will be .parent .child span
. To get the .parent
class we use the list method nth
which takes the array, and the wanted position. In our case we want to loop through $item
and get the first item.
Then we concatenate this first item with our new class, the variable $appended-item
will be .parent.bold
.
To add the rest of the classes, we use set-nth
method which takes the array which index will replace (lists/arrays in SCSS start from 1) and the newly created variable $appended-item
. Now we have the wanted selector as $new-item
variable.
We put the variable inside of the $new-selector
, because as we pointed before the $current-selector
might contain many CSS selectors.
Finally, we can create the CSS styles with @at-root
and the new selector. The @content
adds all styles inside the @include
block.