111

I am having trouble with nesting in Sass. Say I have the following HTML:

<p href="#" class="item">Text</p>
<p href="#" class="item">Text</p>
<a href="#" class="item">Link</a>

When I try to nest my styles in the following I get a compiling error:

.item {
    color: black;
    a& {
        color:blue;
   }
}

How do I reference a type selector before the parent selector when it is part of the same element?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
McShaman
  • 3,627
  • 8
  • 33
  • 46

5 Answers5

170

As Kumar points out, this has been possible since Sass 3.3.0.rc.1 (Maptastic Maple).

The @at-root directive causes one or more rules to be emitted at the root of the document, rather than being nested beneath their parent selectors.

We can combine the @at-root directive along with interpolation #{} to arrive at the intended outcome.

SASS

.item {
    color: black;
    @at-root {
        a#{&} {
            color:blue;
        }
    }
}

// Can also be written like this.
.item {
    color: black;
    @at-root a#{&} {
        color:blue;
    }
}

Output CSS

.item {
    color: black;
}
a.item {
    color: blue;
}
Ethan
  • 4,295
  • 4
  • 25
  • 44
Blaine
  • 2,293
  • 1
  • 13
  • 16
  • 3
    This should be the solution to this question. – Kayhadrin Sep 15 '14 at 06:03
  • This isn't working for me... output is coming out to `.item a.item` for some reason. I tried doing `a#{&}` on it's own too and still same result. – Megaroeny Nov 11 '15 at 16:54
  • @jaminroe are you using Libsass? It will be [fixed in 3.3](https://github.com/sass/libsass/issues/1043). – Blaine Nov 11 '15 at 17:36
  • @Blaine I'm using Visual Studio 2013 with Web Essentials. According to what I've read, it is. Maybe the extension/plug-in needs to be updated – Megaroeny Nov 11 '15 at 18:24
  • 1
    @jaminroe looks like it's using libsass (node-sass) under the hood. If that's the case, you'll need to wait until 3.3. – Blaine Nov 12 '15 at 00:00
  • @Blaine I understand now! Did some research on all that and it makes sense now. I'll do it the old fashioned way or use `@expand`. Thank you! – Megaroeny Nov 12 '15 at 03:27
  • 2
    I have a [slightly tougher version of this](https://stackoverflow.com/q/47417943/444255) to solve, similar, but with _multiple_ classes, that has to be appended to a _tag_ – anyone? – Frank N Nov 21 '17 at 17:01
  • This does not work for me using select and option tags, please see the following fiddle: https://jsfiddle.net/kdc4qqx1/. Anyone? – Aleksander Feb 02 '18 at 17:14
36

The @at-root-only method will not solve the problem if you intend to extend the closest selector up the chain. As an example:

#id > .element {
    @at-root div#{&} {
        color: blue;
    }
}

Will compile to:

div#id > .element {
    color: blue;
}

What if you need to join your tag to .element instead of #id?

There's a function in Sass called selector-unify() that solves this. Using this with @at-root it is possible to target .element.

#id > .element {
    @at-root #{selector-unify(&, div)} {
        color: blue;
    }
}

Will compile to:

#id > div.element {
    color: blue;
}
Ben
  • 5,117
  • 2
  • 27
  • 26
  • 1
    This didn't give me the intended outcome. In Sass 3.4.11, this outputs to `#id > .element #id > div.element { color: blue; }`. Another approach would be to use [`selector_replace`](http://sass-lang.com/documentation/Sass/Script/Functions.html#selector_replace-instance_method), `#id { > .element { @at-root #{selector-replace(&,'.element', 'div.element')} { color: blue;}}}`. [Here's a Gist](https://gist.github.com/blainerobison/94106e7e50d413a5b2a0) for better legibility. – Blaine Feb 09 '15 at 18:55
  • @Blaine You've picked up a glaring bug, thank you. I've edited my answer with a working solution. – Ben Feb 09 '15 at 22:56
  • 1
    That said, `selector-replace` is also another way to do it, but it requires knowing what the selector is. The `selector-unify` method has the benefit of being used in mixins. – Ben Feb 09 '15 at 22:59
  • Awesome answer. – JoannaFalkowska Dec 21 '17 at 13:13
9

For starters, (at time of writing this answer) there's no sass syntax that uses selector&. If you were going to do something like that, you'd need a space between the selector and the ampersand. For example:

.item {
    .helper & {

    }
}

// compiles to:
.helper .item {

}

The other way of using the ampersand is probably what you're (incorrectly) looking for:

.item {
    &.helper {

    }
}

// compiles to:
.item.helper {

}

This allows you to extend selectors with other classes, IDs, pseudo-selectors, etc. Unfortunately for your case, this would theoretically compile to something like .itema which obviously doesn't work.

You may just want to rethink how you're writing your CSS. Is there a parent element you could use?

<div class="item">
    <p>text</p>
    <p>text</p>
    <a href="#">a link</a>
</div>

This way, you could easily write your SASS in the following manner:

.item {
    p {
        // paragraph styles here
    }
    a {
        // anchor styles here
    }
}

(Side note: you should take a look at your html. You're mixing single and double quotes AND putting href attributes on p tags.)

imjared
  • 19,492
  • 4
  • 49
  • 72
  • @Lübnah I agree but I'm not trying to define best practices for micro-performance gains. I'm trying to get someone who is putting a `href` tag on a `p` to write working CSS. – imjared Feb 12 '14 at 14:04
  • Theres absolutely no support for saying &.selector is bad practice at all, there are tons of use cases exactly like the one OP was posting. – Mike Mellor Oct 03 '17 at 13:33
  • @MikeMellor OP wrote `.item { a& }` and there's no sass syntax for that (as far as i'm aware. i suggested he was probably looking for something like the ampersand syntax you described which is used pretty regularly in Sass. however, building on OP's example, `.item { &a }` is not valid and would [compile to `.itema`](https://gist.github.com/imjared/e2bf4d5fc7f6a4151e10cc90ac69e14a). Just like I said in my answer. Is there something I'm missing? – imjared Oct 03 '17 at 13:43
  • OP wanted `.item { a& { ... } }` which now can be done as @Blaine points out with `.item { @at-root a#{&} { ... } }`. The point is if you have a class on the element its easy to do `.item { &.class { ... } }` so why shouldn't you be able to do the same thing with a raw html element. Just because there was no way to do it as of the time OP posted doesn't mean he was incorrect to want the functionality to actually do it. – Mike Mellor Oct 03 '17 at 14:59
6

AFAIK In case you want to combine ampersand for class and tags at the same time, you need to use this syntax:

.c1 {
  @at-root h1#{&},
    h2#{&}
    &.c2, {
    color: #aaa;
  }
}

will compile to

h1.c1,
h2.c1,
.c1.c2 {
  color: #aaa;
}

Other uses (like using the class before @at-root or using multiple @at-roots) will lead to errors.

Hope it'll be useful

Vahid
  • 6,639
  • 5
  • 37
  • 61
  • 1
    I just used this, very useful for mixins where you want to vary the behaviour for span or div elements for example – santiago arizti Apr 10 '18 at 22:27
  • wow - very cool :-) So the `#{&}` is basically tricking the compiler - or will this always work in future!? – Simon_Weaver May 15 '19 at 19:51
  • @Simon_Weaver in sass, everything inside `#{}` is evaluated. Also `&` means current selector. So `#{&}` is evaluated to `.c1`. – Vahid May 16 '19 at 09:25
  • But you’re tricking it in the sense that & alone doesn’t work ;-) – Simon_Weaver May 16 '19 at 17:54
  • 1
    @Simon_Weaver see [this link](https://www.sassmeister.com/gist/7432163723699fec9e1b52bd14ac2460) for more examples of `&` – Vahid May 17 '19 at 04:45
4

This feature has landed in the newest version of Sass, 3.3.0.rc.1(Maptastic Maple)

The two closely related features which you'll need to use are the scriptable &, which you can interpolate within a nested styles to reference parent elements, and the @at-root directive, which places the immediately following selector or block of css at the root (it will not have any parents in the outputted css)

See this Github issue for more details

kumarharsh
  • 18,961
  • 8
  • 72
  • 100