I have the following SCSS for styling links in my menu:
nav {
ul {
li {
a {
color: red
}
}
}
ul.opened {
li {
a {
color: green
}
}
}
}
Which generates the following (correct) CSS:
nav ul li a {
color: red;
}
nav ul.opened li a {
color: green;
}
I tried modifying my JavaScript to apply the class to the nav element instead, and use selector-append()
in Sass to append the class. But that seems to do the appending in the wrong order (and if the arguments are reversed, the class is appended to the last element!):
nav {
ul {
li {
a {
color: red;
@at-root #{selector-append('.opened', &)} {
color: green;
}
}
}
}
}
Output (incorrect!):
nav ul li a {
color: red;
}
.openednav ul li a {
color: green;
}
Is there a way the SCSS can be rewritten so that the class can be correctly appended without having to duplicate selectors (similar to the selector-append()
method)?
Since the element we want to replace has a unique name, what we're looking for is this:
nav {
ul {
li {
a {
color: red;
@at-root #{selector-replace(&, 'ul', 'ul.opened')} {
color: green;
}
}
}
}
}
Manipulating selectors is extremely dirty, and I would advise against it unless you absolutely had to. If you're overqualifying your selectors by specifying things like table tr td
or ul li
, then start by simplifying: tr and ul are both redundant in these selectors (unless you're trying to avoid styling elements under an ordered list). Adjust your nesting to be simpler, etc.
Starting with Sass version 3.4, there are 2 important features that allow you to modify selectors.
Example:
.foo ul > li a, .bar {
$sel: &;
@debug $sel;
}
You'll always get a list of list of strings because selectors can be chained together with a comma, even when you have only one selector.
.foo ul > li a, .bar { ... }
(1 2 3 4 5), (1)
You'll note that the descendant selector is being counted here (lists in Sass can be either space or comma delimited). This is extremely important to remember.
selector-replace()
doesn't workThe selector-replace()
function does not work in the following cases:
ul ul li
)ul ul li
-> ul ul ul li
)ul > li
-> ul li
)In this case, you'll need to loop over the selectors and you'll need to know which position you want to modify. 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;
}
The function we need here takes 2 arguments: the original selector and the selector you'd like to append to it. Uses simple interpolation to do the job.
@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;
}
This function also takes 2 arguments: the original selector and the selector you'd like 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;
}
Removing a selector is as simple as replacing your 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;
}
Selectors are just a lists. Any list manipulation functions will work on it and you can loop over it to modify it as necessary.
So yeah, don't do it unless you really really really need to. If you've decided you still need it, I've packaged these functions up into the selector-nth library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With