Skip to content
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] Conflicts in proposal with Sass/Less #2937

Closed
matthew-dean opened this issue Jul 18, 2018 · 21 comments
Closed

[css-nesting] Conflicts in proposal with Sass/Less #2937

matthew-dean opened this issue Jul 18, 2018 · 21 comments

Comments

@matthew-dean
Copy link

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.

@matthew-dean
Copy link
Author

matthew-dean commented Jul 18, 2018

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.

@matthew-dean
Copy link
Author

matthew-dean commented Jul 18, 2018

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.

@tabatkins
Copy link
Member

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.

@tabatkins tabatkins added the css-nesting-1 Current Work label Jul 19, 2018
@thysultan
Copy link

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 {}

@matthew-dean
Copy link
Author

matthew-dean commented Jul 19, 2018

@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.

@matthew-dean
Copy link
Author

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.

@tabatkins
Copy link
Member

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.

@matthew-dean
Copy link
Author

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.

@chriseppstein
Copy link

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.

@matthew-dean
Copy link
Author

matthew-dean commented Jul 20, 2018

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.

@matthew-dean
Copy link
Author

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.

@vrubleg
Copy link

vrubleg commented Dec 22, 2020

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.

@IanVS
Copy link

IanVS commented Apr 28, 2021

On the topic of confusion, it might be worth adding some examples or discussion directly into the spec to clarify the limitations of the nesting selector not building up simple selectors as some pre-processors do. In particular, there is a line in the spec which can be read in different ways, and might give the impression that it is possible to do, specifically:

An ampersand is unambiguously separable from an ident, tho, so there is no problem with it preceding a type selector, like &div.

I had to read that a few times to understand what it was saying, and it's the only place in the spec that does not include a space after the nesting selector.

@tabatkins
Copy link
Member

I had to read that a few times to understand what it was saying, and it's the only place in the spec that does not include a space after the nesting selector.

A number of examples don't have a space after the nesting selector, such as Examples 2, 4, and 7.

@IanVS
Copy link

IanVS commented Apr 28, 2021

True, I should have been more specific. Those examples contain syntax like . or :, which is pretty clearly valid. I did not see any examples showing that something like this is invalid:

.App {
  &Container { color: blue; }
}

I think that could be useful to include, given that it is valid in preprocessors, and some people may expect that it's valid in css-nesting as well.

@vrubleg
Copy link

vrubleg commented Apr 28, 2021

As far as I understand, your example is valid and equals to:

Container.App { color: blue; }

It is different from Sass, but it is probably more logical.

@mirisuzanne
Copy link
Contributor

For reference, the Sass team has documented exactly where things differ, and how we plan to handle the migration.

(modulo some details of specificity)

While I find it a bit funny to relegate specificity differences to a parenthetical -- authors care a lot about that little detail! -- I do think those differences will be rare, and easy to draw attention to.

@Dan503
Copy link

Dan503 commented Jun 12, 2021

I would probably make @nest mandatory to make it explicit, but the current spec allows to use @nest for every nested rule anyway.

Please don't make @nest mandatory if it doesn't have to be. It adds unnecessary noise to a CSS rule. CSS will look cleaner without it.

It can be a differentiating factor for preprocessors. If the rule has @nest use native nesting, else use preprocessor nesting.

I suspect people will make @nest mandatory through css linting tools but it shouldn't be mandatory at the language level if it doesn't have to be.

@matthew-dean
Copy link
Author

It can be a differentiating factor for preprocessors. If the rule has @nest use native nesting, else use preprocessor nesting.

This is a good point / strategy.

@Dan503
Copy link

Dan503 commented Jun 13, 2021

@matthew-dean that is how Sass plans on doing things. At least in the short term.
sass/sass#3030 (comment)

@tabatkins
Copy link
Member

Wrt character concatenation, we're explicitly rejecting that use-case, as explained earlier in this thread.

Wrt overlapping/clashing with existing preprocessor syntax, the Sass maintainers, at least, have told us not to worry about it, and they'll engineer around any remaining difficulties. I'm going to assume that other preprocessors will end up in a similar situation if they're not already.

Closing as Invalid/Wontfix.
Please open up a new issue if there's something specific we didn't adequately address.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants