Open maranomynet opened 10 years ago
Use interpolation, @maranomynet:
.bar {
foo#{&} {
color: baz;
}
}
Demo: http://sassmeister.com/gist/da2394d0d28b0fe95d59
Works in Sass 3.4.
Interesting hack. It suppresses the error but...
.bar foo.bar { color: baz; }
...is not the desired CSS.
It should be:
foo.bar { color: baz; }
It's not a hack, it's the way meant to be done.
...is not the desired CSS.
In this case, use the @at-root
directive:
.bar {
@at-root foo#{&} {
color: baz;
}
}
Still, the arbitrary discrepancy between this working smoothly:
.bar {
foo & { color: baz; }
}
and this throwing an error:
.bar {
foo& { color: baz; }
}
seems like a bug. (Breaking the principle of least astonishment.)
Using the &
parent selector without interpolation was a temporary feature in Sass 3.3, it is now deprecated.
Sass follows strict logic for how the parent selector works. It is indeed not very intuitive in more complex cases, but there's no sense in flowing against the current.
Neither intuitive nor explained in http://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector
I might be wrong about the deprecation status.
Unfortunately, @chriseppstein did not explain the differences in behavior between the normal and interpolated parent selector. I believe it might be as follows:
&
appears in the selector, the previous selector in hierarchy is reconstructed.&
does not appear in the selector, the current selector is appended to the previous selector.&
is not treated as an appearance.@at-root
directive removes the previous selector, unless it's explicitly inserted via &
.if this is possible:
.bar {
@at-root foo#{&} { color: baz; }
}
wouldn't it be possible (and reasonable!) for SASS to do this automatically for you, whenever it encounters a pattern like this:
.bar {
foo& { color: baz; }
}
??
Using the
&
parent selector without interpolation was a temporary feature in Sass 3.3, it is now deprecated.
This is inaccurate. Using &
with a suffix is still supported. Using it with a prefix (as here), however, has never been supported.
if this is possible:
.bar { @at-root foo#{&} { color: baz; } }
wouldn't it be possible (and reasonable!) for SASS to do this automatically for you, whenever it encounters a pattern like this:
.bar { foo& { color: baz; } }
??
The reason we don't allow this is that foo&
can often produce unclear or simply invalid selectors, depending on the value of &
. In general, &
may only be used anywhere an element selector can be used, since it might be replaced with an element selector. We made an exception for &-suffix
because that was a very common use-case that actually did end up working how people expected most of the time, but I don't think the same is true for &
.
Using #{}
is a better solution here because it makes it explicit that you're doing string-based selector substitution rather than using Sass's selector resolution. I'd suggest you use the new selector functions, though:
.bar {
@at-root #{selector-append(foo, &)} { color: baz; }
}
This will play more nicely with parent selectors containing commas.
@nex3, thank you for clarifications.
But don't you agree that
.bar {
@at-root #{selector-append(foo, &)} { color: baz; }
}
Is way too much typing, compared to LESS'
.bar {
foo& { color: baz; }
}
There should be a simple way to write that. In projects using this convention, people type it like 100 times every day.
There should be a simple way to write that...
+1
I suppose it doesn't produce many more issues than allowing &foo
. I'll mark it as planned.
Thank you for changing your mind, @nex3. People keep stumbling upon this: https://github.com/sass/sass/issues/1436
Yay!
Any idea when this will be implemented? I'm banging my head against this as well. Converted all my LESS files to SCSS because I was told SASS was better than LESS, but was very disappointed to find I couldn't do button&
as a selector to get for example button.something
.
It was issues like this one (and quite a few other similar ones) that pushed me away from SASS over to Stylus. It is incredibly expressive and user-friendly.
As it actually turns out the libsass https://github.com/sass/libsass/tree/13ae62def291c4934ad04d0d527e84b4021d60a3 actually accepts this syntax:
+1
The selector& syntax is really useful for UI widget architectures where classes affecting display of the a widget are set on the outermost DOM element of the widget.
For example let's say I have a list item widget where multiple child elements of the li
have some of their styles overridden depending on whether or not a particular icon is disabled. Rather than modifying each of the child elements I just set a no_icon
class on the li
.
So if my li
element has the class widget_list_item
then the selector& syntax lets me do the following:
.widget_list_item {
a {
padding-right: 24px; /* icon width */
.no_icon& {
padding-right: 0;
}
.title {
margin-right: 0.375rem; /* spacing between title and icon */
.no_icon& {
margin-right: 0;
}
}
}
}
In this simple example flipping it to use &.no_icon { ... }
wouldn't make it harder to read. However once the styles and selectors for different display settings start to accumulate the selector& syntax keeps related styles together, helping with readability and maintainability.
The @at-root syntax would also work here but it's so verbose that I find it makes the SCSS harder to read.
Please keep in mind that @at-root
doesn't always produce the desirable result.
Consider this:
.wrapper {
.field {
input.& { ... }
select.& { ... }
}
}
The suggested workaround becomes:
.wrapper {
.field {
@at-root input#{&} { ... }
@at-root select#{&} { ... }
}
}
Which produces:
input.wrapper .field { ... }
select.wrapper .field { ... }
While I would want it to be:
.wrapper input.field { ... }
.wrapper select.field { ... }
I'm sure it's possible to juggle things around to make Sass output the desired css, but with reusable includes/mixins it becomes unneccesarily hard.
This coincides with the desire for a true parent selector, in addition to the not-parent-but-something-like-context selector &
.
That's a good use-case for selector functions:
.wrapper .field {
@at-root #{selector-unify(&, "input")} {/* ... */}
@at-root #{selector-unify(&, "select")} {/* ... */}
}
you can even make that into a mixin if you want:
@mixin unify-parent($child) {
@at-root #{selector-unify(&, $child)} {@content}
}
.wrapper .field {
@include unify-parent("input") {/* ... */}
@include unify-parent("select") {/* ... */}
}
Okay. From the documentation, I would have never known to use selector-unify
for this. It's unclear to me what that actually does under the bonnet. And trying out your example (it works) I can't figure out how it sort of knows where to put input
and select
, respectectively.
Still, it's quite convoluted to require @at-root
, interpolation, and a function that just feels like black magic.
On top of that, the fact that there is a verbose way of doing things, doesn't mean that doesn't need to be a clear and easier to understand way of doing the same thing.
Okay. From the documentation, I would have never known to use
selector-unify
for this. It's unclear to me what that actually does under the bonnet. And trying out your example (it works) I can't figure out how it sort of knows where to putinput
andselect
, respectectively.
selector-unify()
, like all Sass functions, is documented on the functions page. As the docs say, it returns "a single selector that matches only elements matched by both input selectors". If you have ideas for clarifying that documentation, you're welcome to submit a pull request.
Still, it's quite convoluted to require
@at-root
, interpolation, and a function that just feels like black magic.On top of that, the fact that there is a verbose way of doing things, doesn't mean that doesn't need to be a clear and easier to understand way of doing the same thing.
You're trying to do is pretty advanced selector manipulation, so I don't think it's too surprising that you need to rely on some more advanced features. &
is extremely simple: it textually includes the parent selector. That's easy to explain and easy to reason about. There's a lot of value to that simplicity, especially for users who are new to Sass.
But we recognize that it's not always sufficient for every use-case, so we provide a suite of selector functions and the ability to refer to the parent selector from SassScript. These advanced features make it possible for users to implement more complex behaviors on their own and wrap them up into reusable mixins like unify-parent()
.
Your use-case may not be the same as the next user's, though. Adding easy shortcuts for every single use-case ultimately fights against clarity; you end up with so many shortcuts that users (especially new users) can't keep track of what any given shortcut means. So it's our design philosophy to provide a small set of tools (like @at-root
and selector functions) that can be used by users to build a large set of behavior.
selector-unify(), like all Sass functions, is documented on the functions page. As the docs say, it returns "a single selector that matches only elements matched by both input selectors". If you have ideas for clarifying that documentation, you're welcome to submit a pull request.
The fact that it's there, doesn't mean folks will remember to use it, even if they understand the description. I'm not sure how else to describe it - technically it's correct. Perhaps an example or two will help.
As for the rest, you're right of course. But you also seem to contradict yourself. There is indeed a lot of value to the simplicity of &
, but folks seem to want the additional simplicity of being able to do selector&
. The dilemma is clear though. Keep expanding the capability of &
, or stick with a slightly verbose syntax for things it's not capable of?
One of these choices will not appeal to novice or casual users, which there are a lot of. Advanced users aren't afraid of verbose syntax I'd say, but a team of frontend devs doesn't usually consist of only senior members.
Personally I think that supporting selector&
would be easier for users to understand. Currently the situation is like this:
& selector { } // works
selector & { } // works
&selector { } // works
selector& { } // doesn't work
I do agree that if Sass chooses to support selector&
that it should have a behavior consistent with &selector
. Having selector&
behave like selector-unify()
when &selector
behaves like @at-root
would be even more surprising than it just not working.
To be clear, this issue is marked as "Planned" because we plan to add support for selector&
. It will work the same way as &selector
does.
Have we supported selector& yet ?
I even wrote a small post about this issue: https://medium.com/web-developers-path/using-parent-selector-prefixes-in-sass-5adf22ff7078
It's funny how this is being discussed for a five years already = )
As a previous user of less and stylus, I'd really like to see this implemented!
I'd love to help getting this done as well. Some pointers would be highly appreciated.
Is this a proposal? I cannot find anything resembling this in either https://github.com/sass/sass/tree/master/proposal nor https://github.com/sass/sass/tree/master/accepted
If not - how to go about issues marked as planned?
Also - what is the connection between the milestones that have appeared in this issue (3.4, 4.0) and this repository?
The full process for adding a new feature is described in CONTRIBUTING.md
. Once you've read that, let me know if you have any more questions, or just feel free to open a pull request with a formal proposal.
Perfect, thank you @nex3!
Had a quick glace at it now, and it seems to cover all I need. I'll take a shot at it next session!
There we go! I've most likely missed something and/or made some mistake, but the first draft is out: https://github.com/sass/sass/pull/2722
All feedback more than welcome - my first time doing anything like this - thank you!
Encountered the same problem a few years ago and taking a couple of hours to re-understand it. Like others said, the solution for now is just counterintuitive or verbose. I feel so sad that this issue is still open since it is 2021.
I'm just learning Sass, So as a newbie, I might be wrong but it's clear to me that it's a syntax error. when you want to use &
(parent selector) as a suffix you have to add white space before it. Just like they showed in their documentation
**.alert {
// The parent selector can be used to add pseudo-classes to the outer
// selector.
&:hover {
font-weight: bold;
}
// It can also be used to style the outer selector in a certain context, such // as a body set to use a right-to-left language. [dir=rtl] & { margin-left: 0; margin-right: 10px; } }** see in the first nested they used it as prefix that's why they didn't add any whitespace but in the second nested element they used it as a suffix that's why they used extra whitespace before that. It just seems to me the correct way.
An important update here: the CSS nesting specification is now using foo&
to mean "add the element selector foo
to the parent selector", so we'll need to match those semantics if/when we do add support for this format rather than adding a prefix to an existing selector.
I'm hoping this could be implemented soon. I wrote my own reduced impl. of something like Sass, and, as &-postfixing was automatically available, I've extensively used tricks like the following:
/* simplify selectors with long class prefixes */
/* -> .long-class-name-suffix1.long-class-name-suffix2 {...} */
.long-class-name {
&-suffix1&-suffix2 {
property: value;
}
}
/* increase specificity */
/* .some-class.some-class.some-class {...} */
.some-class {
&&& {
property: value;
}
}
Sadly, because of this issue, neither of these tricks are supported with Sass currently.
According to Can I use..., the :is()
pseudo-class is valid for ~ 95% global users as of November 2023. I believe something like this could work instead:
.class {
&:is(div) {
color: black;
}
h2:is(&__child) {
color: white;
}
}
@mephistia, Thanks for the input. That does work in both my cases, assuming support for :is,
/* simplify selectors with long class prefixes */
/* -> .long-class-name-suffix1.long-class-name-suffix2 {...} */
.long-class-name {
&-suffix1:is(&-suffix2) {
property: value;
}
}
/* increase specificity */
/* .some-class.some-class.some-class {...} */
.some-class {
:is(&):is(&):is(&) { // first :is isn't needed, but maintains the idiom, and c/p is easier
property: value;
}
}
It still would be nice if it wasn't necessary to use :is... &:is(&):is(&)
isn't quite as succinct as &&&
, but at least it is still way better than .very-long-classname.very-long-classname.very-long-classname
.
I also walked into this problem today, as I'm converting from Less to Sass. And to give you an example of 1 of the 59 occurrences we have in our style base:
I see somethings have already been merged, but the issue is still open after 10 years. Is there a timeline or an expected version of npm package in which this will become available?
@cduivis That pattern will never work in Sass, because it's now explicitly against the official CSS semantics for parent selectors. You'll need to write:
.tw-teaser-card {
&--actionable > &__content a[href]#{&}__action {
// ...
}
}
@nex3 Hey thanks for the reply, that work around isn't too bad.
Btw what do you mean with "official CSS semantics for parent selectors"? I haven't seen any proposal in the W3C CSS documentation yet for a parent selector?
I do want to mention that when using the BEM methodology in CSS, that these kind of selector combinations become common place, especially when dealing with more complex / fluid styles.
And I have to confess that somehow it feels odd that we need a special syntax to achieve these kind of things in Sass, while it feels like a natural and obvious code that you would want to write as a styler.
Btw what do you mean with "official CSS semantics for parent selectors"? I haven't seen any proposal in the W3C CSS documentation yet for a parent selector?
Causes an error. (See: http://codepen.io/anon/pen/GIufH)
This is surprising (since
.bar { foo & { ... } }
works like a charm), and this error prevents several useful selector prefixing patterns.