How to do SASS Grandparent Selectors

Writing your SASS in a nested format is a good practice because of improved project organization and separation of concerns. An example:

<div class='component'>
<div class='component__card'>
<div class='component__name'>John Doe</div>
<a class='component__button' href='#'>View Profile</a>
</div>
<div class='component__card'>
<div class='component__name'>Jane Doe</div>
<a class='component__button' href='#'>View Profile</a>
</div>
</div>

And the corresponding SCSS:

.component {
padding: 2vw;
  &__card {
background-color: #FFF;
}
  &__name {
color: #000;
}
  &__button {
background-color: blue;
color: #FFF;
}
}

You probably already know that the Ampersand (&) is the Parent Selector in SASS. It is a shortcut to the elements immediate parent. In this example, the & resolves to .component because that is the immediate parent. The output CSS:

.component {
padding: 2vw;
}
.component__card {
background-color: #FFF;
}
.component__name {
color: #000;
}
.component__button {
background-color: blue;
color: #FFF;
}

I think we can all agree that the Ampersand & is really useful, especially if you are using something like BEM syntax. It allows you to easily change class names on a massive scale if necessarily while keeping the CSS structure of your components organized.

How do you select the Grandparent?

What if you wanted to select not just the parent, but the grandparent? First, let’s think about why we would want to do this:

In our example, let’s say we wanted to change the .component__name color when you hover over it’s parent: .component__card. We could try this:

.component {
  &__card {
&:hover &__name {
color: #BADA55;
}
}

&__name {
color: #000;
}
}

At first glance you might think this would work, but nope. It results in this:

.component__card:hover .component__card__name {
color: #BADA55;
}

Okay so that didn’t work. The parent selector works perfectly for Pseudo-Classes but fail here because we need to select the .component element, which is the grandparent here.

SASS doesn’t have any sort of grandparent selector built-in. Some sort of double-ampersand && type thing would be nice, but it just doesn’t exist.

An alternative approach is this:

.component {
  &__card {
...
}
  &__card:hover &__name {
color: #BADA55;
}

&__name {
color: #000;
}
}

Okay so this isn’t a bad solution. It seems to work. The downsides are that you are getting away from the nested syntax and you are repeating yourself by using &__card in multiple places.

Obviously in this very simplified example, this is very manageable and not a big deal. But in a large code base with dozens of different components with complicated nested SASS structures, deviating from the nested syntax even a little can end up costing you later in maintenance and troubleshooting time.

I’m a big fan and believer of collaborative Open Source projects. To me, it’s important to make sure your code is not difficult for others to pick up and understand how to make changes. If a developer with fresh eyes on a project doesn’t immediately realize that you occasionally break out of the (deceptively consistent) nested syntax, it can lead to a lot of confusion and maybe buggy CSS.

$variables as selectors

Of course you know about variables in SASS. It’s one of the primary reasons for using SASS. It’s great for things like this:

$color: #323232;
.copy {
color: $color;
}
h1 {
border-bottom: $color;
}

But did you know you could also do this?

.component {
$c: &; // The class name is in a variable now
}

How would you use this? You have to use SASS’s Interpolation Syntax. It involves wrapping a variable in #{} in order to echo it out as a string:

.component {
$c: &; // Set the parent as a variable
padding: 2vw;
  &__card {
background-color: #FFF;

&:hover #{$c}__name { // Use the variable here
color: #BADA55;
}
}
  &__name {
color: #000;
}
  &__button {
background-color: blue;
color: #FFF;
}
}

And the output:

.component {
padding: 2vw;
}
.component__card {
background-color: #FFF;
}
.component__card:hover .component__name {
background-color: #BADA55;
}
.component__name {
color: #000;
}
.component__button {
background-color: blue;
color: #FFF;
}

Awesome! It’s a grandparent selector and it works! Obviously you can use this not just for grandparents but for any ancestor up the tree.

What else do we need to know?

First of all, variables stay within the scope they are declared. So this won’t work:

.component {
$c: &;
}
#{$c}__name {  // Undefined variable: "$c".

}

Also, if you don’t use the & in a nested structure you can get some weird selectors (that don’t make sense):

.grid {
$g: &;
  &__column {
$c: &;
color: #000;
  > div {
#{$g}:hover & {
color: red;
}
// Result: .grid:hover .grid__column > div

#{$g}:hover #{$c} {
color: #blue;
}
// Result: .grid__column > div .grid:hover .grid__column
}
}
}

This can be solved by using @at-root:

.grid {
$g: &;
&__column {
$c: &;
color: #000;
> div {
#{$g}:hover & {
color: red;
}

@at-root {
#{$g}:hover #{$c} {
color: #blue;
}
// Result: .grid:hover .grid__column
}
}
}
}

Conclusion

In this article you have learned how to utilize SASS variables as grandparent selectors (maybe they should be called ancestor selectors…). This allows you to stay within an organized nested syntax for better organization and compartmentalization. If you can think of any other clever uses of this approach please feel free to leave a comment!