-
Notifications
You must be signed in to change notification settings - Fork 679
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-2] Syntax to customize how a nest-containing selector is resolved? #6330
Comments
I would probably use something like .component {
color: green;
--child-color: unset;
& .container {
color: brown;
}
&:focus {
--child-color: red;
}
& .container.active {
--child-color: white;
}
& .child {
color: var(--child-color, blue);
&:hover {
color: var(--child-color, yellow);
}
}
} |
I find the proposed solution really difficult to read and process. Indeed, what @Loirooriol suggests would address a lot of these use cases in a far more readable way, but not all, since the state often needs to toggle multiple properties with different types, that cannot (currently, see #5624 ) be toggled with one custom property. I wonder if a better solution for those cases would be some way to reference the |
@Loirooriol that’s an interesting idea, but as @LeaVerou already pointed out, I’d like to define any type or number of properties for a given state (sorry for the overly simplified example). And in this case that approach would basically double the number of lines, as you have to define multiple custom properties in the parents and assign them in the child afterwards. Also, I believe that all styles belonging to the child should be defined in the child. @LeaVerou I’m not totally opposed to reference different levels with something like |
Oh that's a good point, we don't want that. I wonder then if it should be explicit: You'd declare a name for the E.g. something like: .foo {
scope-name: bar;
& .bar {
&bar:focus .baz {
}
}
} Of course lots to figure out: where does |
Would this be the equivalent?
|
That depends on how it's defined. I think |
Ok, so if .foo {
scope-name: foo;
& .bar {
& .baz {
&foo:focus {
}
}
}
} I guess you named the scope In my understanding The issues I see with this approach:
The benefit of the originally proposed I believe any solution will contain three ingredients: .foo {
& .bar {
&:hover {
}
}
} It also provides means to prepend something to the full selector, like: .foo {
& .bar {
@nest :hover & {
}
}
} And you can have media queries right in place (which is great for responsive layouts): .foo {
& .bar {
@media (min-width: 768px) {
& {
}
}
}
} So you can basically do everything you want within an existing nesting – except if the selector in question is accidentally part of the nesting. In this case the only way (currently) is to replicate the modified nesting (and that’s a bad thing for several reasons). It is kind of 'discriminatory' to the selectors that happen to be in the nesting: .foo {
& .bar {
}
&:hover {
& .bar {
}
}
} So I think once nesting is introduced there should not only be means to prepend and append something to |
I don't like being able to arbitrarily replace parts of parent selectors to something completely different. At that point I think you should just move that part outside of the nesting. But being able to reference the elements matched by some ancestor rule (not just the parent) may be reasonable. Using a property (or descriptor, I guess?) seems strange, though. I would add some syntax just after the selector, like .foo => &(foo) {
& .bar {
& .baz {
@nest &(foo):focus & {}
}
}
} Note that
Also, I think you would still need to use a plain |
I actually don’t necessarily want to replace parts of the parent selectors either, but I’d like to be able to insert arbitrary modifications. Would your proposed approach allow something like this: .foo => &(foo) {
& .bar {
& .baz {
@nest &(foo) .qux.active & {}
}
}
} as an equivalent to this? .foo .qux.active .bar .baz {} I’m not sure about the And at that point I’m wondering why we couldn’t select the ancestor directly? This would modify all occurrences of .foo {
& .bar {
& .baz {
@nest &(.foo):focus & {}
}
}
} |
Well, technically I think it would be like
But it's not clear what's the relationship with
Probably, if it's at the beginning, I guess. |
I guess a possible problem is if you have <div class="foo"><div class="bar"><div class="foo"><p>Foo</p></div></div></div> .foo => &(foo) {
& .bar {
& p {
@nest &(foo):focus & {}
}
}
} Then this will be like |
Well but sadly in that case it’s kind of useless, you could simply write that with existing syntax: .foo {
& .bar {
& p {
@nest .foo:focus & {}
}
}
} is equivalent to .foo:focus .foo .bar p {} This is prepending to |
I’m basically looking for something like that: .foo {
& .bar {
& p {
&:mod(:ancestor(.foo):focus) {} /* => .foo:focus .bar p */
}
}
} |
@Loirooriol @LeaVerou I realize that if my claim that the nesting spec is incomplete holds true, then the conclusion must be to focus on the existing .foo {
& .bar {
& .baz {
&:hover {
/* => .foo .bar .baz:hover */
}
@nest :hover & {
/* => :hover .foo .bar .baz */
}
&(.foo:hover) {
/* => .foo:hover .bar .baz */
}
}
}
}
I believe that appending a selector to an ancestor is equally important as prepending a selector to the nesting selector and thus the burden to work around this situation should not be passed onto the users of CSS. Instead, there has to be a way that allows users to append selectors to ancestors right in place. Without a syntax that facilitates this, users have to recreate slightly modified nestings various times – which contradicts the whole point of nesting as an approach to avoid redundancy. |
for me, when I get into scenarios like the above, where i find myself nested kinda deep and needing some specialty syntax adjustments, i trade the deep forming DRY roots for legibility. aka, what @Loirooriol did. less assembling for my brain to do in that moment and less for my brain to do later when i come back. coming back to complex nesting i've made is troublesome, though seemed right in the moment. i wonder if the complexity cliff here is appropriately ~3-4 deep, where these syntax adjustments tend to be requested, but is also at a point one should consider if they need to nest that deep and maybe there's a more legible / simple way to achieve the style? it might behoove the native nesting spec to have a simpler (than less, stylus, scss) offering with less power but also less debt and complexity? custom props are definitely an escape hatch here, because a selector can change a prop and any children can hook into. that being said, i'm not opposed to finding a way for |
Yup, I'm pretty strongly of the opinion that at the point you feel like you need this sort of thing, you should be thinking about how to rewrite it to be simpler instead. "Limit your nesting to 3-4 deep" seems to be a pretty common and reasonable bit of advice in the CSS preprocessor world, and when you're at that point the pain of possible repetition is low compared to the gain of remaining straightforward and readable. At minimum, I'd block this from the first version of Nesting; we can always add it in a Nesting 2 if, after wide usage, it seems like it's still something useful and necessary. |
Thanks to you all for thinking this through! Just to clarify, this issue is not about deep nesting, it’s about nesting by itself. It strikes me that Yes, there are workarounds, but that’s the whole point of the issue: the nesting mechanism should be powerful enough to be able to handle all possible combinations in a straightforward, readable, and DRY way – without the need to resort to custom properties etc. Assuming you write the following in a nested way:
Then the nesting spec should be able to handle the following situations:
None of these situations is more basic or more important than the others, some of them are just easier to represent in a nested way. But all of them exist and should be handled properly. |
It is more complex, which is why all nesting solutions do the simple thing but the extra thing you're asking for is an advanced non-universal feature. ^_^
As someone who loves designing features to be as powerful and generative as possible, I have to object that this is not a given. If there is a straightforward, readable way to achieve all of these, great; but we can't take it as given that such a way exists. In particular, I think the In particular, nesting as it exists in the current spec doesn't require you to understand the structure of the parent selector(s) itself; all you need to know is what elements have been selected by the parent selector. You can then build a new selector based on that, with (For example, I think it would be fine if you had some selector-building mechanism in JS, where you could store selectors in objects and chain them together into new selector objects. You could then store a significant early selector in a well-named variable, to indicate that it's important to the reader, and then modify it later on.) In any case, my earlier point holds:
|
Just wanted to cross-reference the discussion in #6977 |
My comment was in #6977 was:
It was upvoted by @argyleink and @romainmenke @lubomirblazekcz . If we are broadening this issue to include suggestions like these, we should probably edit the title to reflect this. |
I realized that the .container {
.widget {
color: blue;
:has(.container.selected) & {
color: red; /* Workaround for .container.selected .widget { color: red } */
}
}
} Upcoming .container {
.widget {
color: blue;
@scope (&) {
.container.selected :scope {
color: red; /* Workaround for .container.selected .widget { color: red } */
}
}
}
} Please note that in Chrome v110 even the following works (https://codepen.io/flachware/pen/OJoMXPM): .container {
.widget {
color: blue;
.container.selected & {
color: red;
}
}
} |
Yes, that is correct per spec. Nested selectors are only interpreted as relative if they start with a combinator, or you don't use the |
This is ingenious! So, according to the spec the example above desugars as: .container .widget { color: blue; }
.container.selected .widget { color: red; } Whereas in preprocessors it would currently desugar as: .container .widget { color: blue; }
.container.selected .container .widget { color: red; } If I’m not mistaken this issue becomes obsolete as a result? |
Still has the same problem as #6330 (comment) |
To stick with your example: <div class="foo"><div class="bar"><div class="foo"><p>Foo</p></div></div></div> There are many ways to exclude the inner .foo {
.bar {
& p {
color: blue;
.foo:focus * & {
color: red; /* Outer .foo only */
}
/* or */
.foo:focus .bar & {
color: red; /* Outer .foo only */
}
/* … */
}
}
} |
No, not quite. It desugars to the equivalent of It does indeed differ from how current preprocessors desugar. In your later example, tho, <div class=foo>
<div class=bar>
<div class=foo tabindex=-1 autofocus> <!-- this element is focused -->
<div> <!-- some unrelated, non-.bar element -->
<p>I'm red.
... Whether this is a problem in practice depends on your markup. |
Oh that’s even more ingenious, as Right, so the spec desugars to the equivalent of However, if I’m not mistaken your last example is not accurate: the trick here is that |
Sorry, you're right, my markup needs one more wrapper between the focused .foo and the p. But notably it doesn't need to be a .bar. I'll fix, one sec. But your selector with the |
Agreed. But if I’m not mistaken this is a general problem and not limited to nesting. The following example has the same problem: .foo:focus p { color: red; } But there are ways to solve that: .foo:has(> .bar):focus p { color: red; } If this is unsatisfactory the upcoming Coming back to the original issue, as the CSS nesting spec resolves .component {
color: green;
.container {
color: brown;
}
.child {
color: blue;
&:hover {
color: yellow;
}
.component:focus & {
color: red;
}
.container.active & {
color: white;
}
}
} Hence this issue is good to close (imho). |
I’m very exited about the upcoming nesting feature (https://drafts.csswg.org/css-nesting/). But with nesting new troubles emerge. This is a proposal for a Sass-like
:selector-replace()
pseudo-class function that allows authors to modify the nesting selector&
.In complex user interfaces elements tend to have multiple states which depend in part on parent elements. The deeper an element is nested the more likely it becomes that you have to define a state that is based on a modified selector somewhere in the middle of the 'selector chain'.
Example:
is equivalent to
In this simplified example the
.child
element has four states, two of these states depend on its ancestors. Of course this could be written by adding these states to.component
and.container
. But this would not be DRY and the.child
styles start to scatter around, in the worst case you would have to replicate the nesting multiple times in various places just for one modified part at a time (and there would be a lot more nesting levels and elements in reality).The proposed
:selector-replace()
pseudo-class function addresses this issue by allowing you to modify nested selectors wherever you need it. I do not propose that particular name, I think something like:mod()
(modify) might make more sense with regard to brevity.Disclaimer: I did not come up with this feature, it is part of Sass – for good reason.
The text was updated successfully, but these errors were encountered: