Change the middle of the selector in Sass (add / remove classes, etc.)

I have the following SCSS for link styles in my menu:

nav { ul { li { a { color: red } } } ul.opened { li { a { color: green } } } } 

What generates the following (correct) CSS:

 nav ul li a { color: red; } nav ul.opened li a { color: green; } 

I tried changing my JavaScript to apply the class to the nav element instead, and use selector-append() in Sass to add the class. But this is like adding in the wrong order (and if the arguments are reversed, the class is added to the last element!):

 nav { ul { li { a { color: red; @at-root #{selector-append('.opened', &)} { color: green; } } } } } 

Output (wrong!):

 nav ul li a { color: red; } .openednav ul li a { color: green; } 

Is it possible to rewrite SCSS so that the class can be correctly added without duplicating selectors (similar to the selector-append() method)?

+7
css-selectors sass
source share
2 answers

Short answer

Since the element that we want to replace has a unique name, we are looking for:

 nav { ul { li { a { color: red; @at-root #{selector-replace(&, 'ul', 'ul.opened')} { color: green; } } } } } 

Long answer

Manipulating selectors is extremely dirty, and I would advise him to do it, unless you needed it. If you redefine your selectors by setting things like table tr td or ul li , then start with the simplification: tr and ul are redundant in these selectors (unless you try to avoid styling the elements in an ordered list). Adjust your nesting easier, etc.

Starting with Sass 3.4, there are two important functions that allow you to change the selector.

Example:

 .foo ul > li a, .bar { $sel: &; @debug $sel; } 

You will always get a list of string lists, because selectors can be associated with a comma, even if you have only one selector.

 .foo ul > li a, .bar { ... } (1 2 3 4 5), (1) 

You will notice that the descendant selector is counted here (lists in Sass can be either spaces or commas). This is very important to remember.


If selector-replace() not working

The selector-replace() function does not work in the following cases:

  • The selector you want to replace is not unique (e.g. ul ul li )
  • Do you want to insert one or more selectors (e.g. ul ul liul ul ul li )
  • Do you want to remove the selector (e.g. ul > liul li )

In this case, you will need to iterate over the selector, and you will need to know which position you want to change. The following function will take a function and apply it to a specific position in your selector using the magic of the call () function.

 @function selector-nth($sel, $n, $f, $args...) { $collector: (); @each $s in $sel { $modified: call($f, nth($s, $n), $args...); $collector: append($collector, set-nth($s, $n, $modified), comma); } @return $collector; } 

Add a class (when the selector is not unique or you do not know its name)

The function we need contains 2 arguments: the original selector and the selector you want to add to it. Simple interpolation is used to complete the task.

 @function append-class($a, $b) { @return #{$a}#{$b}; } .foo, .bar { ul > li a { color: red; @at-root #{selector-nth(&, -2, append-class, '.baz')} { color: blue; } } } 

Output:

 .foo ul > li a, .bar ul > li a { color: red; } .foo ul > li.baz a, .bar ul > li.baz a { color: blue; } 

Insert selector

This function also takes 2 arguments: the original selector and the selector that you want to insert before it.

 @function insert-selector($a, $b) { @return $b $a; } .foo, .bar { ul > li a { color: red; @at-root #{selector-nth(&, -2, insert-selector, '.baz')} { color: blue; } } } 

Output:

 .foo ul > li a, .bar ul > li a { color: red; } .foo ul > .baz li a, .bar ul > .baz li a { color: blue; } 

Delete selector

Removing the selector is as simple as replacing the selector with an empty string.

 @function remove-selector($sel) { @return ''; } .foo, .bar { ul > li a { color: red; @at-root #{selector-nth(&, -2, remove-selector)} { color: blue; } } } 

Output:

 .foo ul > li a, .bar ul > li a { color: red; } .foo ul > a, .bar ul > a { color: blue; } 

TL; DR

Selectors are just lists. Any list manipulation functions will work on it, and you can iterate over it to change it as needed.

So yes, don't do this if you really really don't need to. If you decide what you still need, I have packaged these functions into the selector nth library .

+10
source share

I made a mixin that solves this problem.

Github: https://github.com/imkremen/sass-parent-append

Example: https://codepen.io/imkremen/pen/RMVBvq


Usage (scss):

 .ancestor { display: inline-flex; .grandparent { padding: 32px; background-color: lightgreen; .parent { padding: 32px; background-color: blue; .elem { padding: 16px; background-color: white; @include parent-append(":focus", 3) { box-shadow: inset 0 0 0 8px aqua; } @include parent-append(":hover") { background-color: fuchsia; } @include parent-append("p", 0, true) { background-color: green; } } } } } 

Result (css):

 .ancestor { display: inline-flex; } .ancestor .grandparent { padding: 32px; background-color: lightgreen; } .ancestor .grandparent .parent { padding: 32px; background-color: blue; } .ancestor .grandparent .parent .elem { padding: 16px; background-color: white; } .ancestor:focus .grandparent .parent .elem { box-shadow: inset 0 0 0 8px aqua; } .ancestor .grandparent .parent:hover .elem { background-color: fuchsia; } .ancestor .grandparent .parent p.elem { background-color: green; } 
0
source share

All Articles