-
Notifications
You must be signed in to change notification settings - Fork 683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[css-nesting] selecting grandparent selector with @nest #6977
Comments
Adding a bit more context from The example above fed to the plugin.
Source .a {
color: blue;
& .b {
@nest :not(.c)& {
color: red
}
}
} Result
Source .a {
color: blue;
& .b {
@nest :not(.c)& {
color: red
}
}
} Result
This behaviour is not intended as a feature or an operating mode. The flag exists to give CSS authors the option of having CSS output without We could do a better job explaining that this flag should be avoided as it introduces two issues : specificity and complex selector matching. Currently it reads more like a feature if you do not have in depth knowledge of the nesting spec. There is now also a plugin for However for complex selectors there is not much we can do. Part of the issue here (imho) is that nesting has existed for so long in CSS pre-processors and that there are certain expectations of how it work. These do not always match the actual spec. Without any actual implementation CSS authors also do not have a good reference point. Maybe more can be done to explain and illustrate the feature for CSS authors? I think the suggestions by @LeaVerou in #6330 are really insightful and would introduce an explicit mechanism to reference the selector you intend. |
The suggestions by @LeaVerou in #6330 are insightful yes, but maybe to much complex. To be clear, following behaviour in postcss was working even prior .a {
color: blue;
& .b {
@nest :not(.c)& {
color: red
}
}
}
.a {
color: blue
}
.a:not(.c) .b {
color: red
} That why I though it was something that was part of the spec previously. Which is not the case, hence why I've created this issue. I think this syntax for selecting grandparent is less complex and it would benefit the spec. There are a lot of cases where this might be useful, for example .ui-input {
& input {
@nest :not(.is-active)& {
color: red
}
}
}
.ui-input:not(.is-active) input {
color: red;
} Once you are nested little deeper (for example in pseudo selector too), you have no simple clear way of accessing parent selector, for example component with different states. Here are also some real world examples in UI library written in nesting syntax - https://github.com/newlogic-digital/ui/blob/main/src/styles/Ui/Input.css#L200 |
Can you clarify with comments in your source CSS which selector each To me it is not fully clear what "selecting grandparent" means. .grandparent {
& .parent { /* "&" references ".grandparent" */
& .child { /* "&" references ".grandparent .parent" */
color: red;
}
}
} Only set a style on .grandparent {
& .parent { /* "&" references ".grandparent" */
@nest .grandparent:hover & .child { /* "&" references ".grandparent .parent" */
color: red;
}
}
} equivalent to But then a new addition to the spec to write this without needing to add Taking the first example from #6330 : .grandparent {
& .parent { /* "&" references ".grandparent" */
/* "&1" references ".grandparent" */
/* "&" references ".grandparent .parent" */
@nest &1:hover & .child {
color: red;
}
}
} This final example would be equivalent to :
Which matches this html <div class="grandparent">
<!-- possible with extra html elements in between -->
<div class="parent">
<!-- possible with extra html elements in between -->
<div class="child"></div>
</div>
</div> |
I just found out that following syntax is also possible to select grandparent in grandparent {
& .parent { /* "&" references ".grandparent" */
@nest :hover > & .child { /* "&" references ".grandparent .parent" */
color: red;
}
}
} equivalent to And to be honest, according to spec I would expect the result be It might be possible to select grandparent, but it might not.. it's not very clear in spec right now. EDIT: |
Can you reference the spec sections that imply this? grandparent {
& .parent { /* "&" references ".grandparent" */
@nest :hover > & .child { /* "&" references ".grandparent .parent" */
color: red;
}
}
} This can have unexpected effects. works as intended : <div class="grandparent"><!-- CSS matches when this is hovered -->
<div class="parent">
<div class="child"></div>
</div>
</div> does not work as intended : <div class="grandparent">
<div class="other"><!-- CSS matches when this is hovered -->
<div class="parent">
<div class="child"></div>
</div>
</div>
</div> |
@romainmenke yes that is what I mean, it is not well defined. According to spec, there is no wrapping Also you are correct, that solution could have unexpected effect, hmm :/ |
First, I have to say I keep stumbling on use cases for this. Things like: .container {
& .widget {
@nest .container.selected & {
/* FAIL, gets rewritten to .container.selected .container .widget,
not .container.selected .widget */
}
}
} Also, I agree my proposal in #6330 (comment) is overkill. Also, it introduces a CSS property that is not actually applied on any elements, but is just used to evaluate syntax. Yikes. Instead, I think we should go for a simpler solution, with predefined names for going up 1, 2, 3, ... levels. Perhaps .container {
& .widget {
@nest &1.selected & {
/* Gets rewritten to .container.selected .widget */
}
}
} Is |
Whatabout .container {
& .widget {
@nest &:parent.selected {
/* Gets rewritten to .container.selected .widget */
}
}
} Still this as an advanced usage, and maybe needed only in few cases. Selecting top level grandparent is much common use as I mentioned previously. But that is not clear if it's possible with the current spec or not - that is my main concern |
.container {
& .widget {
@nest .container.selected & {
/* FAIL, gets rewritten to .container.selected .container .widget,
not .container.selected .widget */
}
}
} Doesn't that get rewritten to this : .container.selected :is(.container .widget) Which would actually match this : <div class="container selected">
<div class="widget"> .container {
& .widget {
@nest &1.selected & {
/* Gets rewritten to .container.selected .widget */
}
}
} Becomes : :is(.container).selected :is(.container .widget) or : .container.selected :is(.container .widget) |
These example mostly have simple selectors, no compound or complex selectors. The example with Maybe it should be written as : |
Personally if you're going to go the &1, &2, &3 route I'd argue that & should be all of them and &0 should be the last child selector, so .container {
& .widget {
@nest &1.selected &0 {
/* Gets rewritten to .container.selected .widget */ .container {
& .widget {
@nest &.selected {
/* Gets rewritten to .container .widget.selected */ The Because forcing this is a recipe for disaster: .container {
& .widget {
&1 &.selected {
&2 &1&:hover {
/* Becomes .container .widget.selected:hover */ Particularly if you have to remember which ones have spaces and which ones don't. Unless the suggestion is that Most of the bugs resulting from |
@Griffork I think there is some confusion here :) In general I don't think it is or should be possible to start writing a completely unrelated selector with nesting. For example : .container {
& .widget {
@nest &1.selected {
color: red;
}
}
}
You can simply write this : .container {
&.selected {
color: red;
}
} The issue brought up here is that you might want to manipulate two parts of a selector when nesting. example : Only apply a color to Works with todays spec : .container {
/* base styles for .container */
& .widget {
/* base styles for .widget in .container */
&:hover {
/* base styles for .widget:hover in .container */
}
}
&.selected .widget:hover {
color:red
}
} Or : .container {
/* base styles for .container */
& .widget {
/* base styles for .widget in .container */
&:hover {
/* base styles for .widget:hover in .container */
@nest .container.selected & {
color:red
}
}
}
} But in both cases you have repetition of either It would not change how |
@lubomirblazekcz relevant comment on the use of |
@romainmenke I see, but it's still something that is not very clear in spec here - https://www.w3.org/TR/css-nesting-1/#direct and that was my main concern |
I fully agree now that it is not clear and reads almost like a note on the side and less like a critical part of any implementation. I was first looking for a good reference where this was discussed. @tabatkins Is this something that could be clarified more in the spec and the examples there? After we updated postcss-nesting to desugar with |
Regarding the gradparent selector, here is another example how it's done in postcss currently https://github.com/toomuchdesign/postcss-nested-ancestors Personally I would go with something more simple, as it was said - referring to the whole of the parent selector is the most common use-case. |
Do you mean the outer most selector? this is complicated without concrete examples or very specific wording :) .container {
& .widget {
/* whole parent : .container */
/* outer most selector : .container */
&:hover {
/* whole parent : .container .widget */
/* outer most selector : .container */
}
}
} Or the parent of edit : re-reading the preceding comments I now understand you mean : .container {
& .widget {
@nest &<something>.selected &:hover {}
}
} Where I think the proposal by LeaVerou is an elegant and non-ambiguous solution. I do wonder if this complicates implementing nested selectors? Not relevant here, but this behaviour would make a postcss plugin much more complex. |
Maybe the previous 'lucky' behaviour of postcss-nesting, would be simpliest to implement spec-wise, .a {
& .b {
@nest :not(.c)& {
color: red
}
}
}
.a:not(.c) .b {
color: red
} But I agree that |
@lubomirblazekcz please enlighten to me (css noob) why your proposal would work? Why does this: .a {
& .b {
@nest :not(.c)& {
color: red
}
}
} become: .a:not(.c) .b {
color: red
} and not: :not(.c).a .b {
color: red
} Is it because the second example is not valid css? Does the & behave differently based on whether or not the css is valid? What if I do this: .a {
& .b {
@nest .fail& {
color: red
}
}
} does that become: .a.fail .b {
color: red
} or (what I'd expect): .fail.a .b {
color: red
} If it becomes Also weirdly in my mind I'd expect if As I said before, I'm not a fan of & working differently in different scenarios, but since you also seem happy with the |
.a:not(.c) .b {
color: red
} and :not(.c).a .b {
color: red
} Is the same, only the order is changed and it works in all browsers, if it's the valid syntax i'm not sure to be honest. I'm only pointing out how it works (worked) in .a {
& .b {
@nest :not(.c) & {
color: red
}
}
} equals :not(.c) .a .b {
color: red
} And if you remove the space between |
@lubomirblazekcz Maybe more interesting to reference
This will be clearer in examples and discussions here as people can then try those tools today and see how they work. This also avoids a circular mutation : |
@romainmenke yes I agree. I only referenced postcss because that implementation previously worked as a bug, so that's why it might be easy to adapt. But I might be totally wrong. |
By my reading of the spec, this construction already has an unambiguous meaning.
I think I got all that right, anyway. The other case bottoms out, by my understanding, into something vaguely close to the desired output, but much more permissive by matching
Here is something that simplifies in the desired way, but requires the repeating of the
In this case, This is where I see the semantic and syntactic holes lining up. Since the syntax that motivated this request already has a simple, consistent meaning, we would need new syntax to ease the repetition burden. I think the direct solution would be a way to refer to outer selectors, for instance allowing Another possibility: Custom selectors would solve the repetition problem generally, at the cost of a little cruft to define the selector name. Being able to place a hypothetical
This way, there would be no truly new syntax (just a novel combination of two proposed standards). Parsing custom selectors, I think, doesn't require much more than expanding the syntactic sugar, but there could also be some dark corners there. At any rate, it's something a preprocessor could support as long as it properly wraps the referent of |
The spec does not currently allow nesting |
The In this case, though, that detail just makes things nice to use without having to resort to shadow DOM shenanigans. You could just as easily define
|
@custom-selector :--granny .a
:--granny {
& .b {
@nest :--granny:not(.c) & {}
}
} Is this intended to become : :--granny:not(.c) :is(:--granny .b) {} or : :--granny:not(.c) .b {} I do like the idea of using |
Technically it would expand to the former, but because the You could instead do all sorts of wacky things that don't simplify to something so intuitive. |
Re: the original comment #6977 (comment) I believe there was some confusion about how triply (and deeper) nested rules worked, seemingly based on an intuition of nesting working via string concatenation. In the OP's example
Re @LeaVerou's comment #6977 (comment) ; this is something we think might be worth exploring in the next level, and is currently filed as issue #6330. @LeaVerou and @Griffork, maybe you can copy your suggestions into #6330? Re @romainmenke's comment #6977 (comment) The spec was somewhat clarified, to make it clear that the nesting selector is defined by matching elements, not by desugaring; the :is() desugaring is just given as an example of equivalent behavior. <https://drafts.csswg.org/css-nesting-1/#nest-selector> Lastly, any comments about the custom selectors spec should be filed separately against that spec. :) Closing out this very meandering combo issue, please re-file individual specific problems as individual, specific issues if we missed anything! |
The current spec might seem to have missing grandparent selector, which is also very used feature in many preprocessors.
This is currently still possible with postcss-nesting, with
noIsPseudoSelector: true
option. Which results in following:This is also the same way Less uses grandparent, and Sass uses
@at-rule
My question, is this something that should also be added to spec? Becuase I didn't find any mention of this in there. Or should some different syntax be proposed for selecting gradparent selector? Any thoughts on this @tabatkins ?
This issue is also related to grandparent selector - #6330
Also related postcss issue here - csstools/postcss-plugins#195 and supposedly grandselector syntax that previously worked in postcss-nesting was a bug
Current way that works in postcss-nestning is following, which I am not sure if is corect
The text was updated successfully, but these errors were encountered: