Is there a SASS rule for outputting a descendant to the root?
You're looking for @at-root
:
#example-id-1 {
background: blue;
@at-root #example-id-2 {
background: red;
@at-root #example-id-3 {
background: yellow;
}
}
}
Note that this is a Sass 3.3 feature.
Prevent nesting in SCSS/SASS output
I think you're looking for the @at-root
directive.
It works by ‘jumping out’ of where you nest it in your Sass to be a the top level.
For example, you could to this :
.image {
color: #333;
@at-root {
.imageWrapper {
color: #666;
img {
color: #999;
}
}
}
.stayNested {
background-color: #555;
}
@at-root {
.textWrapper {
color: #aaa;
title {
color: #ccc;
}
caption {
color: #fff;
}
}
}
}
That would compile to this :
.image {
color: #333;
}
.imageWrapper {
color: #666;
}
.imageWrapper img {
color: #999;
}
.image .stayNested {
background-color: #555;
}
.textWrapper {
color: #aaa;
}
.textWrapper title {
color: #ccc;
}
.textWrapper caption {
color: #fff;
}
For more info, see the official documentation
SCSS ancestor sensitive selector
I would advice against it*... but you can use nth on selector lists - like:
*makes style less readable
SCSS
.parent1,
.parent2 {
.element {
/* all items in selector list */
@at-root #{nth(&, 1)} {
/* first item in selector list */
}
@at-root #{nth(&, 2)} {
/* second item in selector list */
}
}
}
CSS Output
.parent1 .element,
.parent2 .element {
/* all items in selector list */
}
.parent1 .element {
/* first item in selector list */
}
.parent2 .element {
/* second item in selector list */
}
Update
To make it a bit more readable you could create a helper mixin à la:
@mixin selector-contains($substring) {
@each $part in & {
@if str-index($part+'', $substring) {
@at-root #{$part}{ @content; }
}
}
}
.parent1,
.parent2 {
.element {
@include selector-contains('.parent1'){
/* first item in selector list */
}
@include selector-contains('.parent2'){
/* second item in selector list */
}
@include selector-contains('.parent'){
/* all elements */
}
}
}
SCSS Import Relative to Root
- If you are using Angular CLI, take a look at Global styles,
"stylePreprocessorOptions"
option specifically. - For webpack, you can configure
includePaths
in sassLoader plugin configuration. - And it's similar for gulp builds, you pass
includePaths
to the sass plugin.
Whatever your build tool is, sass will treat those paths as root, so you can import them directly, so with:
includePaths: ["anywhere/on/my/disk"]
you can just @import 'styles'
instead of @import 'anywhere/on/my/disk/styles'
.
Append the parent selector to the end with Sass
Sass 3.2 and older
The only thing you can do is reverse your nesting or not nest at all:
.social-media {
/* ... */
.twitter {
/* ... */
}
.facebook {
/* ... */
}
}
ul.social-media {
/* ... */
}
Sass 3.3 and later
You can do that using interpolation and the @at-root
directive:
.social-media {
/* ... */
// Here's the solution:
@at-root ul#{&} {
/* ... */
}
}
However, if your parent selector contains multiple selectors, you'll need to use selector-append
instead:
.social-media, .doodads {
/* ... */
// Here's the solution:
@at-root #{selector-append(ul, &)} {
/* ... */
}
}
Output:
.social-media, .doodads {
/* ... */
}
ul.social-media, ul.doodads {
/* ... */
}
Modifying the middle of a selector in Sass (adding/removing classes, etc.)
The short answer
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;
}
}
}
}
}
The long answer
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.
- Selector functions
- The parent selector can be stored in a variable
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.
When selector-replace()
doesn't work
The selector-replace()
function does not work in the following cases:
- The selector you want to replace is not unique (eg.
ul ul li
) - You want to insert one or more selectors (eg.
ul ul li
->ul ul ul li
) - You want to remove a selector (eg.
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;
}
Append a class (when the selector isn't unique or you don't know its name)
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;
}
Insert a selector
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;
}
Remove a selector
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;
}
TL;DR
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.
Related Topics
Margin-Top Percentage Does Not Change When Window Height Decreases
Make Text in Select Element Wrap When Too Long
Set Textfield Height Material-Ui
CSS - Opaque Text on Low Opacity Div
Create Line After Text with CSS
How to Increase the Bullet Size in a Li
Nesting CSS @Supports and @Media Queries
Page-Break-* Doesn't Work on Google Chrome
Why Doesn't Font Awesome Work in My Shadow Dom
Css3Pie in MVC, Where to Place the Pie.Htc File
Opera and Custom Cursor in CSS
Leaflet for R: How to Change Default CSS Cluster Classes
CSS Fluid Layout: Margin-Top Based on Percentage Grows When Container Width Increases
Clamping Lines Without '-Webkit-Line-Clamp'
Bootstrap Collapsed Menu Not Pushing Content Down When Expanded