Sass Reference - - By Hugo Giraudel

Selector Nesting

Selector nesting is a way to create compound selectors by nesting selectors within each others. Basically, instead of being able to write nothing but CSS declarations in rulesets, it makes it possible to write inner rulesets, that will then get compiled at root-level with the relevant selectors.

// The following selector nesting
.parent {
    color: red;

    .child {
        color: blue;
    }
}

// ...is strictly equivalent to
.parent {
    color: red;
}

.parent .child {
    color: blue;
}

This feature is double-edged as it is a very convenient way to write modular scoped CSS, but also makes the code harder to read and can possibly lead to unexpected selectors when misused or overused. The general opinion is to avoid going deeper than 3 levels. This is often referred to as the Inception Rule, a clever wink to Christoper Nolan’s movie where it is not possible to go deeper than 3 dreams nested within each others.

Current Selector Reference

Along with selector nesting, Sass provides an internal “variable” (&) accessible anywhere but at the root of the document that is a reference to the current selector. In previous version of Sass, & could not be manipulated like an actual variable, although this is now completely valid as of Sass 3.4. Please refer to the Selector manipulation functions section for extra information on how to deal with the reference selector in a programmatic way.

// At root-level, `&` does not exist

.foo {
    color: blue;

    // At this point, `&` means `.foo`
    &:hover {
        color: red;

        // At this point, `&` means `.foo:hover`
        &::after {
            color: gold;

            // At this point, `&` means `.foo:hover::after`
        }
    }
}

.bar {
    color: yellow;

    // At this point, `&` means `.bar`
    .baz & {
        color: lightgreen;

        // At this point, `&` means `.baz .bar`
    }
}

[code language='sass']

<p>The selector reference is extremely useful for pseudo-classes and pseudo-elements as it can keep everything about a selector gathered within the same ruleset (in the Sass stylesheet).</p>

<p>From a technical standpoint, the <code>&amp;</code> selector (in newer versions) is a list of lists. The top-level list contains compound selectors (separated with commas), while the inner lists contain parts of selectors (separated with spaces).</p>

[code language='sass']
.foo, .bar a {
    // & -> (.foo, (.bar a))
}

Because the reference is nothing but a list, it is possible to use native Sass list functions on it. For instance, you could use length to know the number of compound selectors, or nth to access a specific compound selector (if more than one).

.foo, .bar a {
    length: length(&);
    // -> 2
    last-compound-selector: nth(&, -1);
    // -> .bar a
    last-selector: nth(nth(&, -1), -1);
    // -> a
}

Crafting New Selectors

An interesting feature that comes with the current selector reference is the fact that it can be used to create entirely new selectors at root-level. This might look a bit weird at first, but it quickly becomes quite natural when you think of this as a way of namespacing components.

// The following selector nesting
.foo {
    &-bar {
        color: red;
    }
}

// ...is strictly equivalent to
.foo-bar {
    color: red;
}

By using the reference as part of a string, you implicitly tell Sass to create a new selector for it to be output at root-level of the document. This is ideal when working with a BEM-like notation.

.block {
    // Block styles

    &__element {
        // Element styles
    }

    &--modifier {
        // Modifier styles
    }
}

Selector manipulation functions

Since Sass 3.4, the current selector reference (&) is no longer an immutable value. It is now possible to interpolate it, inspect it, manipulate it and update it on the fly. To make it easier to play with selectors, especially the special one, there are a couple of native functions available to manipulate selectors:

  • selector-nest($selectors…): nests selector beneath one another like they would be nested in the stylesheet.
  • selector-append($selectors…): appends selectors to one another without spaces in between.
  • selector-extend($selector, $extendee, $extender): extends extender within $selector.
  • selector-replace($selector, $original, $replacement): replaces replacement within $selector.
  • selector-unify($selector1, $selector2): unifies two selectors to produce a selector that matches elements matched by both.
  • is-superselector($super, $sub): returns whether sub does, and possibly more.
  • simple-selectors($selector): returns the simple selectors that comprise a compound selector.
  • selector-parse($selector): parses a selector into the format returned by &.

These functions, no matter how powerful they can be, are mostly targeted at frameworks developers who have to handle complicated dynamic selector manipulation. It is quite unlikely that you will ever need any of those functions in ‘everyday’ stylesheet.

Example

a {
    &:hover {
        // Hover styles
    }

    &:active {
        // Active styles
    }

    &:focus {
        // Focus styles
    }

    &:visited {
        // Visited styles
    }
}

Engine compatibility

Selector nesting is fully compatible across all Sass engines and there is no known bug to this day about its implementation. Note that Sass prior to 3.4 cannot manipulate the current selector reference in any way. Also, selector manipulation functions did not exist before Sass 3.4.