_Note: I originally posted this on another thread, and was asked to re-post as a new issue._
I worry that the use of & in the CSS nesting proposal is going to lead to a lot of confusion, since it doesn't work like Sass's / Less's &. So half your code may need to be flattened, and half can maybe preserve &. When writing in Sass or Less, how do you target which is which? The preprocessing languages don't lose all usefulness, so both of these ecosystems, if this were adopted, would have to re-invent their entire & syntax / usage if people want to target nested selectors as output. Which is a shame, because that would slow adoption and probably lead more to reject using this feature.
I don't have a better syntax suggestion, but I also don't see this (native CSS nesting) as necessary. But, obviously as a Less contributor, I'm biased.
Most "best-practice" style-writing I've seen suggests not really nesting at all, but to use single class identifiers in a BEM-way, like:
.component {
&--header { }
&--body {}
&--button {
&:hover {} //etc
}
}
This keeps things organized and provides single class matching for the browser. But this isn't the way the nesting proposal works. I do get why adopting that form (partial class appending) would be rejected by a CSS spec, because CSS is looking to define complete selector matching, but that just points to a preprocessor still being relevant, which points to a potential conflict around & usage and output. I think a different syntax is needed.
One second thought, I _do_ have a proposal. I've been tinkering with a styling language that avoids the problem of &
littered all over the place by doing an implicit &
, and requiring any nesting to use explicit combinators. Descendent combinators require the explicit >>
symbol vs. simple whitespace.
As in:
.component {
:hover {} // .component:hover
.modifier{} // .component.modifier
> .child, >> .grandchild {} // .component > .child, .component .grandchild
+ .component {} // .component + .component
}
This doesn't address placing the inherited / parent selector after the nested selector, but is that very CSS-y anyway? That's certainly not how the nested @media
proposal works. IMO nesting should work similar to nested @media
, where it appends a new matching condition. Adding a placeholder of where to append doesn't really make sense for CSS. String/selector manipulation is really the job of a preprocessor, if needed.
It also doesn't necessarily solve at all how Sass/Less would produce the above output, but at least it avoids entirely the semantic confusion of borrowing &
from those languages.
And to answer the question of how to handle the above on the pre-processing side, I imagine Less would do something like a compile flag to support nesting and force &
to be explicit for flattening output. Such that:
.component {
:hover { a: b; }
&:hover { a: b; }
}
Would produce the following output (with nested output flag on for Less compiler):
.component {
:hover {
a: b;
}
}
.component:hover {
a: b;
}
That's _much_ easier to support than trying to decide which &
s get kept and which ones get used to flatten output.
Most "best-practice" style-writing I've seen suggests not really nesting at all, but to use single class identifiers in a BEM-way, like:
On the other hand, as expressed in the previous thread, the Sass maintainers consider that syntax (building up a single simple selector thru concatenation of characters) to have been a serious mistake that they wish they could undo. @chriseppstein explicitly stated that he's happy that CSS Nesting is rejecting this possibility.
Stepping away from the argument-from-authority, tho, that syntax is also intrinsically ambiguous. There's no way to tell, given a selector like &foo
, whether that's meant to be adding a foo
type selector to the parent selector, or if it's meant to be appending the characters "foo" to the identifier of the last simple selector in the parent selector. If you assume BEM is being used, you can make some assumptions that will often work (which is what Sass currently does), but CSS can't reasonably make such assumptions. --header
might legitimately be the tagname of an element; the fact that it's currently not a valid HTML element name doesn't mean it wont' be in the future, or that it's not valid in some other language using CSS.
One second thought, I do have a proposal.
Unfortunately, this doesn't solve the entire reason I have the "&
must be first` requirement - the nested selector can be grammatically ambiguous with a declaration, requiring unbounded lookahead to decide which it is.
Also, this proposal is even further away from current preprocessor syntax, so I don't think it satisfies your original goal either.
Would it be possible to satisfy the ambiguity of the BEM case with a function variant.
.a, b {
&(--c) {}
& --d {}
}
yeilds
:matches(.a--c, .b--c), :matches(.a, .b) c {}
@tabatkins
On the other hand, as expressed in the previous thread, the Sass maintainers consider that syntax (building up a single simple selector thru concatenation of characters) to have been a serious mistake that they wish they could undo
AFAIK, the Less maintainers do not. 🤷♂️
@chriseppstein explicitly stated that he's happy that CSS Nesting is rejecting this possibility.
I don't disagree. As I said, class name manipulation is best left up to a CSS preprocessor. CSS should deal with complete selectors.
Also, this proposal is even further away from current preprocessor syntax, so I don't think it satisfies your original goal either.
That was kind of my point. Echoing the syntax of preprocessors but producing a different outcome is semantically confusing. It would be the same if CSS had adopted @var
or $var
for variable names vs --var
, but had changed the way those vars are evaluated. Moving further away from current preprocessor syntax should be the goal of this proposal IMO.
Don't get me wrong. All the CSS preprocessing languages take risks every time syntax is invented that's co-mingled with regular CSS, and that's not necessarily the CSS WG's responsibility to avoid it. I'm just calling for pragmatism, and helping provide an avenue for preprocessors to actually support native nesting, if adopted. The way the proposal sits now, it's not really possible to support it in the preprocessor, which, while not your problem, perhaps unnecessarily limits the goal of it actually being used.
And while I'm an advocate of Less, to me &
natively in CSS is kind of clunky. Ultimately, my spitballed proposed may not be workable, but it's sort of beside my main point, which is that &
is problematic. That's my main point.
To be clear, I think native nesting in CSS is not a bad idea. I'm just hoping it can be introduced in a way that isn't incompatible with a lot of existing stylesheets out there.
AFAIK, the Less maintainers do not. man_shrugging
Sure, which is why "argument from authority" wasn't the whole point. ^_^
That was kind of my point. Echoing the syntax of preprocessors but producing a different outcome is semantically confusing.
Ah, but Nesting doesn't do that. The intersection of Nesting and Sass syntax produces the same result in both (modulo some details of specificity), except when concatenation comes into play. (Because Sass designed their syntax well, not because of any concerted effort at matching them from me.) If you're ignoring concatenation, then I'm unsure what your objection is.
And if this is your concern, then your proposal also fails your criteria, because its intersecting syntax (in particular, stuff like .foo { :hover {...}}
) is interpreted differently than the preprocessors.
And if this is your concern, then your proposal also fails your criteria, because its intersecting syntax (in particular, stuff like .foo { :hover {...}}) is interpreted differently than the preprocessors.
It does, currently. It's just easier to polyfill (i.e. flag) that difference in behavior than a native &
IMO. Although... I guess it might be possible to figure out what's resolvable and allow a compiler flag to leave some things nested...? 🤔 Or maybe a preprocessor just would never output nesting and continue to flatten rulesets, and then I guess someone could decide if they need a preprocessor in the first place if nesting is their only desire.
I guess my thinking is this: say at some point at X time in the future, CSS nesting comes to pass.
Stylesheet author comes to Sass/Less/PostCSS and says, "Okay, I've written these nested rules, and I know I wrote &
but I meant a native &
not a preprocessor &
, so how do I output them as nested rules?" I'm not sure there will be any obvious solution there. It's an unresolvable syntax conflict, since it can't mean both "produce a native nested ruleset" and "flatten selectors". On the one hand, it's great that the CSS syntax is inspired by a popular preprocessor feature, and by what people are using, a sort of paving of the cowpaths, but I worry it paves the cowpath by killing the cows.
That said......... maybe the inclusion of @nest
resolves this? If @nest
or some indicator of native nesting was always present, then it would be resolvable, I think, at the preprocessor level, because it's essentially an inline syntax flag. 🤔 The word @nest
is a little awkward (@has
? @matches
?), but something like that maybe makes this a non-issue, especially if it were always required, either by CSS or by a preprocessor to "flag" native nesting output. It still wouldn't absolve conflict, since the &
within the @nest
rule would still carry a slightly different semantic meaning co-mingled with other &
uses in a stylesheet, but I guess then it's resolvable.
Ideally, though, the feature just wouldn't use &
. Just my $0.02.
I worry it paves the cowpath by killing the cows.
Preprocessors can change. Preprocessor syntax can be deprecated and transformed, often automatically. Browser specs are forever. Toolmakers need to keep the long view here. Authors deserve to have the best syntax that can be provided natively by browsers. Tools like Sass and Less have a much shorter half-life and need to get out of the way when the advances that we've championed become welcomed standards.
The cows are the authors, not the tools. Build tools like Sass and Less are the cowpaths.
Pave the path.
Preprocessors can change. Preprocessor syntax can be deprecated and transformed, often automatically. Browser specs are forever. Toolmakers need to keep the long view here. Authors deserve to have the best syntax that can be provided natively by browsers. Tools like Sass and Less have a much shorter half-life and need to get out of the way when the advances that we've championed become welcomed standards.
@chriseppstein Fair. Is this the best syntax though? I don't know. I feel like you and I have made different points about &
as its currently used in preprocessors being problematic for different reasons. You've pointed out issues w/ concatenation/partial classes, which, while I disagree its an issue in preprocessors, have my own grudges with &
-soup as somehow being a necessary feature of nesting. It's the path preprocessors went down, but it's not the only way to declaratively describe nesting. It's just one way. So we're both advocating for following a different path, despite working on and advocating those languages. Therefore, there is at least something there to look at in regards to room for improvement.
The idea (nesting) is sound. The cowpath is maybe the right cowpath, but maybe it's not the right paving stones, so to speak. I just want to make sure the door is open for exploration of a better solution, instead of seeing the problem (lack of nesting) and immediately gravitating to just one implementation that addresses the problem, just because we're used to seeing that solution as being "the one" that addresses that problem. That's how I feel about the &
syntax.
The cows are the authors, not the tools. Build tools like Sass and Less are the cowpaths.
Basically this ^^. The historical syntax that Sass/Less (and now PostCSS plugins) have used should be set aside, with a fresh look at the problem. What's most relevant is that those tools identify the problem. Starting with the problem, from square A, what is the most appropriate syntax solution? I don't know that anyone can say what that is yet.
Just a comment to add to this issue, in case someone comes across this. Related to:
Stepping away from the argument-from-authority, tho, that syntax is also intrinsically ambiguous. There's no way to tell, given a selector like &foo, whether that's meant to be adding a foo type selector to the parent selector, or if it's meant to be appending the characters "foo" to the identifier of the last simple selector in the parent selector.
Having spent the last year re-writing a Less parser that extends a valid CSS parser (and having to deeply study more of the CSS spec to do so) I now agree with this statement by @tabatkins and more of the points made here about grammatical ambiguity.
Both Less & Sass parsing have unfortunate amounts of ambiguity (and exacerbates some unfortunate CSS grammatical ambiguity) due to nesting syntax, not just what &foo
should mean, but determining, for example, if the start of a rule is, for example, a qualified rule or a declaration.
Current spec is close to perfect. I would probably make @nest
mandatory to make it explicit, but the current spec allows to use @nest
for every nested rule anyway.
If authors of CSS preprocessors want to allow mixing of the native CSS nesting and SCSS-like nesting in one file, they could rely on @nest
. If there is @nest
before nested selector, it means that a user wanted to use native CSS nesting. Nobody prohibits to use Sass / Less when the native CSS nesting is supported by browsers, if behavior of Sass / Less suits better.
Most helpful comment
On the other hand, as expressed in the previous thread, the Sass maintainers consider that syntax (building up a single simple selector thru concatenation of characters) to have been a serious mistake that they wish they could undo. @chriseppstein explicitly stated that he's happy that CSS Nesting is rejecting this possibility.
Stepping away from the argument-from-authority, tho, that syntax is also intrinsically ambiguous. There's no way to tell, given a selector like
&foo
, whether that's meant to be adding afoo
type selector to the parent selector, or if it's meant to be appending the characters "foo" to the identifier of the last simple selector in the parent selector. If you assume BEM is being used, you can make some assumptions that will often work (which is what Sass currently does), but CSS can't reasonably make such assumptions.--header
might legitimately be the tagname of an element; the fact that it's currently not a valid HTML element name doesn't mean it wont' be in the future, or that it's not valid in some other language using CSS.Unfortunately, this doesn't solve the entire reason I have the "
&
must be first` requirement - the nested selector can be grammatically ambiguous with a declaration, requiring unbounded lookahead to decide which it is.Also, this proposal is even further away from current preprocessor syntax, so I don't think it satisfies your original goal either.