I was hoping the new CSS syntax for nesting would work just like in Sass, simple and intuitive. Unfortunately, it's apparently too ambiguous and inefficient for parsing.
> Nesting style rules naively inside of other style rules is, unfortunately, ambiguous—the syntax of a selector overlaps with the syntax of a declaration..
> It is, in most cases, eventually possible to tell properties and selectors apart, but doing so requires unbounded lookahead in the parser; that is, the parser might have to hold onto an unknown amount of content before it can tell which way it’s supposed to be interpreting it.
> CSS to date requires only a small, known amount of lookahead in its parsing, which allows for more efficient parsing algorithms, so unbounded lookahead is generally considered unacceptable among browser implementations of CSS.
Implicit @nest every time for me .. except for example 8 where brackets (finally) made some sense. Also intrigued by the example 9 `&[open]` edge case. Wild.
Honestly tho, I think scss has it right where ampersand is only used for grouping / parenting.
Perhaps I simply don't see the ambiguity after having used Scss, Less and the like for a long time, but isn't the simple `&` by far the best option here? The other two are absurdly verbose without offering any real benefit.
Afaict the lookahead problem refers not to the proposals, but to a syntax without these extra markers altogether. Which is why all the proposals have some markers.
Except that syntax requires infinite lookahead to parse.
It's feasible when you're running a preprocessor on your own code one time, but not feasible for the browser to do on unknown code on every stylesheet it encounters.
I'm quite sure some edge cases could be forbidden to speed ip parsing. The most common use in my experience is simple nesting ("& .child"), and I would argue other forms should be avoided for readability reasons anyway.
But finding 3 other solutions (because of "performance"), not promoting them through transpiling, and then forcing them on web devs seems... googley. Not the first improvement they forced down our throats and unfortunately not the last. /rant
You can just bound the lookahead on the browser by some large number, and accept smaller performance loses for parsing. Anyway, if your possibilities are only nested selector and property, why don't you compute both until they are not ambiguous anymore? You don't need backtracking.
Jake Archibald points out [1] section [2] in that page that explains it somehow. Haven't grokked it yet, but I see that popular SASS (SCSS)-BEM way of doing
.block {
&__element {
&--modifier {
p: v;
}
}
}
for getting
.block__element--modifier { p: v; }
could really be problematic from CSS parser perspective. Also in SASS `&` really is kinda placeholder and I think you can do stuff like
.x { &+&+& { p:v; } }
to get
.x + .x + .x { p: v; }
what again seems problematic. (But again, I don't say I've grasped that fully.)
If the preprocessors can remove the ambiguities, so could the browsers. If the concern is speed, then just stick with the status quo (my preference, but sadly not an option in the poll).
This. And if you want a different solution, offer it through transpiling, and if people like it, then implement it in the browsers. Not the other way around, forcing on us a standard that noone likes.
Otherwise just use sass, it is well-liked and proven. Or, as you suggest, do nothing.
But why? Why does it HAVE to be the existing syntax or nothing? If we know that this syntax would make performance worse, why is an alternative that doesn't do so a worse option than not having this useful feature?
> But why? Why does it HAVE to be the existing syntax or nothing?
Because time and again userland implementations of useful functionality are better, more ergonomic and practical than anything committees come up with.
- the nested CSS proposal supports everything the SCSS syntax does, plus some potentially useful combinations
- the SCSS syntax would literally lead to a worse user experience, both for the end user whose browsing performance is now degraded, and anyone implementing parsers for CSS (since the infinite lookahead is quite a bit more complicated)
- the committee isn't just "coming up" with the new syntax, they are asking for feedback. Let's not pretend this is the same as a committee deciding something without taking input from the affected users.
I understand that generally a de facto standard will be more useful than a de jure one. But this isn't some committee coming up with their own convoluted version of a standard because of NIH - they are communicating clearly and openly around why the established standard would be problematic. Shouldn't we as a technical community try to find the best solution for the problems we face, instead of taking principled stands and ignoring the technical hurdles in our way?
I think option 1 is the worst, because it's not actually optional; it's required precisely when css selectors become complex. That the kind of gotcha that just makes everything harder to explain and read.
I hope the proposal fails entirely, and that no nesting syntax is adopted. It's not worth it, and nesting in SCSS is abused more often than not anyhow.
There are a few exceptions like pseudo-elements and media queries that are always harmless, but most nesting is just asking for specificity gotchas later, or component refactoring surprises.
Looks like another round of W3C bike-shedding an existing easy-to-use and well-understood community standard until it's godawful to work with and half as useful.
No, these are the kind of people who are deliberating on the best syntax to support millions of projects using CSS for the next 30 years. They have to factor in backwards compatibility, extensibility, performance, security, accessibility, responsiveness, and lots of other NFRs.
Once it’s out there, it has to be supported for a long time. It’s worth deliberating to get right. Nobody will die because they took their time.
One of the goals for the web in general is to not impose the burden for that kind of strictness onto users. It's why we reversed course away from XHTML, which would refuse to render a page if the markup was incorrect.
Put another way: inconveniencing developers is preferable to inconveniencing users.
These all feel like an overengineered solution in search of a problem, with some degree of tunnel vision in that all three proposals try to support the same broad set of capabilities.
It looks like somebody decided that nesting support must somehow support "reverse nesting", eg. that you'd like to define what happens to .foo when it's inside a .bar, but nested inside the definition for .foo:
.foo {
.bar > .foo {
...
}
}
Does anybody actually want this? It feels counter-intuitive and weird, and with the syntaxes proposed, seems to provides little benefit compared to just doing it as the old ".bar > .foo" without nesting.
If you drop the requirement for nesting to work in reverse, you can just go with the proven SCSS syntax which is extremely clear, straightforward to use, and requires 0 extra characters typed.
EDIT: Apparently the '&' or some other special token is required to prevent the need for unlimited lookahead while parsing. This is actually explained behind a folded caption in the article, which I initially missed.
I would expect "@nest @media (width => 30em) & {" for consistency with how it works everywhere else, but apparently @media is already special anyway in the "@nest" proposal.
I agree that this syntax could be convenient, though I'm actually a fan of having all the "@media" changes in one place, which is what the current syntax forces.
Apologies if I misunderstand, but your "reverse nesting" syntax already works with scss. I.e. `.a{ .b & {}}` => `.b .a {}` ... I'd quite honestly use it fairly often, usually with state/data modifiers on an ancestor (or ancestor sibling!)
Perhaps it was my own CSS preferences speaking, where I try to limit "spooky action at a distance" for consistency. But yeah, a".selected" state on an ancestor seems like a good use-case for this if you have different tastes. I stand corrected and see how that's useful.
I assume that for the same reason that they cannot support direct nesting with no extra tokens (parsing issues), they also cannot support the simple SCSS version and instead went for the "@nest .b & {" mouthful.
This is a flawed approach and leads to N:M combinatorial explosion in stylesheets. Sure, it’s convenient to have component styles react to the context they’re in, but CSS Custom Properties are a much better solution for that.
FYI, this `:not(nav) a` doesn’t do what you almost certainly intended. It doesn’t match “an <a> that is not inside a <nav>”, but rather “an <a> that has at least one ancestor that is not a <nav>”. You can’t currently express what you wanted there; the best you can generally manage is if you can anchor it with a parent selector, e.g. `:not(nav) > a`, which will match any <a> whose immediate parent is not a <nav>. I can imagine something along the lines of `a:not(:has(nav :scope))` working in the future too.
To play devil's advocate: there's a case to be made that reverse nesting is precisely the the _most reasaonable_ case for nesting, and that forward nesting is the one to be avoided.
The problem with nesting is that it is seductively easy to write, but encourages two coding issues that cause maintenance headaches later on. Firstly, it encourages high selector specificity, and that means potentially enjoying specificity battles later on. Secondly, it encourages tightly coupled DOM structure with CSS - and tight coupling makes later development more expensive.
Both of these issues are significantly less serious when using reverse nesting than forward nesting.
Forward nesting allows for expressing the concept of "parts of a component" by it's DOM structure, so when the component is tweaked, selectors that aren't explicitly tied to any part of a component suddenly break. But reverse nesting is more natural to use in the context of expressing a special case; i.e. my thingmabob is generally green, but in this specific context it's grey.
So on coupling, reverse nesting is less risky: whil it's not impossible to "compress" your css and thus introduce coupling between component structure and a reverse nested selector, it's not a very natural mistake to make.
And on specificity too, reverse nesting is less likely to cause surprises - after all, since you're expressing a more specific situation, you're almost always likely to want the nested selector to win that specificity battle.
So, I'd argue that reverse-nested selectors are actually one of the few general class of nested CSS selectors that are unlikely to cause maintenance headaches later on. Similarly harmless are nested media selectors (again, because nesting implies expressing an exception), and nested pseudo-elements (because there structure and CSS are intrinsically coupled an expressed within the CSS anyhow).
What you call "reverse nesting" is actually the only best good kind of nesting. QED?
Just piping up to say yes, absolutely want the ability to put the `&` anywhere. Your example isn't exactly correct, what I use it for is adjusting styles of the element I'm referring to based on a parent class. The `&` isn't automatically prepended - it puts the element reference in the spot it sits in the CSS. In your example, the `.bar` would be the parent class that is adjusting the styles of the current element.
For: `@nest .bar > & { ... } }` (just using the first syntax for simplicity here)
That doesn't translate to: `.foo { .bar > .foo { ... } }`
I see that many didn't read the hidden part of the post that explains why SCSS version can not be used.
Here's the relevant excerpt.
> ## Why can’t everything be directly nested?
> Nesting style rules naively inside of other style rules is, unfortunately, ambiguous—the syntax of a selector overlaps with the syntax of a declaration, so an implementation requires unbounded lookahead to tell whether a given bit of text is a declaration or the start of a style rule.
> For example, if a parser starts by seeing color:hover ..., it can’t tell whether that’s the color property (being set to an invalid value...) or a selector for a <color> element. It can’t even rely on looking for valid properties to tell the difference; this would cause parsing to depend on which properties the implementation supported, and could change over time.
> Requiring directly-nested style rules to use nest-prefixed selectors works around this problem—an & can never be part of a declaration, so the parser can immediately tell it’s going to be parsing a selector, and thus a nested style rule.
> Some non-browser implementations of nested rules do not impose this requirement. It is, in most cases, eventually possible to tell properties and selectors apart, but doing so requires unbounded lookahead in the parser; that is, the parser might have to hold onto an unknown amount of content before it can tell which way it’s supposed to be interpreting it. CSS to date requires only a small, known amount of lookahead in its parsing, which allows for more efficient parsing algorithms, so unbounded lookahead is generally considered unacceptable among browser implementations of CSS.
Thanks for pointing this out, I indeed missed that (it's hidden behind a folded caption).
It still doesn't explain why all the proposals feel the need to be more powerful than SCSS nesting (allowing "reverse nested" definitions where the element being nested in is not at the start), but it does explain the need for "&".
EDIT: Actually, it looks like SCSS supports the same thing by using '&' anywhere in the definition. I'll just stop posting since it looks like my knowledge is too rusty to contribute positively.
This seems to slap the property on html, whereas the proposed nesting only uses the parent as a selector, and specifies properties for the nested element (if I'm not confusing something again).
If scss can do `.parent & {...}`, that would be the same as the proposal.
The color:hover example seems like a pretty wild edge case. I'd be interested to see a more solid example of a 'boundless' lookahead requirement. In my simplistic view surely a strict mode that requires semicolons after properties (as opposed to commas or curly brackets after selectors) would do the trick?
Edit: I put together (painfully typed out on a phone) an example regex[1] to use the color:hover example. It isn't perfect, but it also isnt boundless.
The problem is not to tell selector from definition (of course it can, otherwise grammar would be non-reducible). The problem is how far in an array of tokens the distinction can be seen.
Parsers know the current state and where to go next out of previous reductions and a few lookaheads which they can use at places known to be clear of ambiguity in a ~fixed number of lookaheads. Parsing something that diverges only down few pages of tokens is problematic size/speed/algo-wise, but the question is how much does that cost really and who should pay.
Edit: But honestly I think this is all wrong direction. Any technology, format, method has 1. features and 2. form. Form is irrelevant because you can make tools to convert any form into any. Features are important because you can’t add features by tooling. But $subj adds zero features, only a new form which is already feels like a compromise. Why do that then? We have to brush our tools instead, e.g. create default nginx-scss plugin, standalone scss caching servers etc. Then everyone could use extended syntax without resorting to all-in-one monstrosities like webpack.
Yeah this is my intuition as well, though I haven't spent much time thinking about the problem. Like, yeah, if you write very non-performant code, it won't perform well. So what? Don't write stupid code.
To understand why this is a problem even with “good” css, one has to understand how parsers work. It doesn’t matter that all css will be good in practice. The entire theoretical possibility of a problematic case (i.e. unbounded number of lookaheads) changes a class of parser required to parse it. Not all grammars are equal and they may require different methods. One grammar may be LALR(const) compatible, and the other may be parsed only with GLR. There is no easy way to use a dumb and quick parsing method once you commit to a grammar (not to a way of writing sources!) in which ambiguity can survive for an indefinite time, even if it’s always short in practice.
You have no idea whether to parse that as a property (setting `color` to that string) or as a nested selector (a span below a div below .qux below .bar below .foo below a color element that's being hovered over). You only know that it's a property if you see a `;` or a `}`, or a nested declaration if you see a `{`.
So yeah, the ambiguity might not be resolved for a pretty long time. And, unless there's some aspect to CSS's grammar I'm not thinking of, this ambiguity exists for every single property (even for stuff like `background-color: blue` -- the parser shouldn't know that `background-color` isn't an HTML element or that `blue` isn't a selector).
It really wouldn't surprise me if adding unbounded lookahead to every single CSS property would cause a pretty significant parsing slow-down.
I guess I'd just like to see a good example. CSS is fairly process intensive, not to parse, but because it directly effects the render. Surely compiling a sheet with some lookahead magic isn't going to explode anything that you couldn't already explode with a 'will-change'?
No way to measure that because the potential inefficiency is infinite - one would need to first work out the likelihood of very large unbounded lookaheads occurring in the wild.
It's really less about benchmarking and more about managing risk: large unbounded lookahead in scss might crash a single concurrent pod in your CICD - isolated, detectable and really easily remedied. The same here could crash many users' browsers - not so easily fixed.
Can you elaborate exactly where the unbounded lookahead is required? I genuinely can't see it. I also don't see why a dramatic update like this couldn't also introduce / require a 'strict' style to be considered valid.
I feel like we're talking serious edge cases here.
I'm not sure I can think of a better explanation/examples than the gp. It essentially comes down to ambiguity between single element selector names (which are unprefixed in CSS - no dot or hash) and property keys (also plain identifiers).
The only similar nested braces form we have in current CSS is always @-prefixed. There's no unprefixed equivalent for parent blocks in current syntax (plus those blocks are currently limited to a single level which I guess mightt simplify things further given the prefixed selector is always at root level)
> I also don't see why a dramatic update like this couldn't also introduce / require a 'strict' style to be considered valid.
I think it could and I think the call for input is open to that as a possibility. The above comment is just referring to a fully compatible direct scss implementation of nesting.
I get your point. However it relies on an example that actually requires an unbounded lookeahead. This is where I'm confused. Sure, these ambiguous blocks (I'm not sure how many examples there are other than color:hover) aren't prefixed, but they are conclusively postfixed. So any lookahead required should be easily bounded. Where does the unbounded aspect come into it?
Edit: I should add that I don't doubt that those involved know what they're talking about. I know nothing of briwser-level CSS parsing. I am certain it is a nightmare. I would really just like a better example to explain the unbounded aspect. Especially when it is being given as the primary excuse to cripple / greatly obtusify a syntax I'm being asked for an opinion on. Maybe it's just me but I would expect that an evolution of this magnitude would afford a bit more compromise elsewhere.
I think when we talk about "unbounded lookahead," it means the lookahead can't be bounded by any constant. A common type of parser is called LR(1), meaning it can only lookahead by 1 character.[0]
The advantage of only looking ahead by a constant is that parsing can be done in linear time. For a backtracking parser, you have to look ahead by up to n characters each time, leading to a quadratic runtime in the worst case.
Seriously tho it seems like a pretty major feature to be introducing. Why not do it right? Surely the complexity should be concealed rather than out front being dealt with in user space.
I assume that the requirements for a browser implementation are more strict than for existing pre-processors such as Sass because 1) the performance budget is much more limited for interactive use compared to pre-processing and 2) browsers need to handle adversarial inputs gracefully. Are there any other reasons?
One potential reason is, SCSS only needs one implementation, and whatever that implementation does is SCSS. If you write `color:hover { ... }` it doesn't really matter whether SCSS happens to parse that as a `color: hover` followed by an illegal `{` token or if it happens to parse it as a nested selector; whichever the parser chooses is fine. But in a standard, with multiple implementations, you need to strictly resolve all ambiguities in the spec so that all implementations parse CSS the same way; just avoiding ambiguities in the grammar is by far the easiest way to achieve that.
Plus, different ways of implementing a parser could have different ways of resolving ambiguities. Maybe parsing `color:hover {}` as a selector happens to be super easy in Firefox's existing parser, but would require a rewrite of Chromium's parser, for example.
It's not a question of implementation power. It's about spec requirements. Until now CSS didn't require more than 1 token to know where we are. Basically, parser doesn't need to backtrack at all.
SCSS apporach, for instance, requires look ahead.
Consider: color:hover over some random markup &
Currently CSS can be sure whether that is a selector or a property depending where it finds it. If it's in a ruleset or at-rule block, it's a selector. If it's in a declaration list, it's a property-value pair.
Now, with nesting it becomes confusing. Parsers can not rely on the state to decide what they're looking at.
One way to fix this is, like many suggested here, to require look-ahead from parser. Which can be a solution in a well defined environment. Unfortunately, the Web is not one of them. We have many underpowered kiosk-type devices that probably can not spare arbitrarily large look-ahead buffers.
Another solution is to somehow let the parser know what it's looking at. That's why leading & is a good indicator it's a selector. & is not used for anything else in CSS. But for selectors where it's not first we need @nest (or anything else that unambiguously marks a selector).
Look-ahead requirements are bad for another reason, too. It makes CSS platform-dependent. Currently any version of CSS clearly states what it provides. Implementations can be partial but they can not claim they support CSS 2.1, for example, when they don't implement 100% of it. Wit look-aheads things become murkier. An implementation can support 100% of the spec but depending on the look-ahead required to parse a stylesheet they might not work. Currently, I don't think there are any CSS features that depend on platform limitations, at least not for parsing. Requiring a look-ahead buffer to parse a stylesheet can be very problematic. Imagine, Twitter got hacked and their embeds started stylesheet have a couple selectors that require 1M tokens look-ahead buffer. How that would break every site that embeds tweets?
Parsing is a really difficult-to-parallelize task. There's no obvious simple way to chunk up the task, since a `{` at line 1 might affect the parsing of a construct on line 100000.
What you could conceivably do is to have a very simple parser whose only job is to have just enough understanding of CSS to be able to split the CSS document into chunks, and then have a proper parser which works in parallel on the chunks produced by the first parser. I can't imagine this would be faster on most code though, considering how simple the full CSS parser is today, and it still requires a single thread which scans through and parses the entire CSS document.
Regardless, browsers are already really multi-threaded. When you parallelize parsing in this way, even if parsing gets a bit faster in isolation, you're increasing the amount of computation necessary to parse the CSS (i.e spending 1 second on one core is less computational work than spending 0.5 seconds on 4 cores). Your parallel CSS parsing will be taking cycles away from HTML parsing and javascript execution.
Thanks, I couldn't get it through the link since it's behind a "text fragment" link which only works on Chrome[1], a bit ironic if you ask me when we are discussing a standard :)
I needed to scroll to the section manually since my browser doesn't support it as shown clearly in the caniuse link I shared. It doesn't work for me and I provided general evidence, so how is it accessible on any browser?
It doesn't. If there is no ampersand it is assumed to start with `& `. So `.childclass` is equivelent to `& .childclass` and if you want something else you need to do `&.additionalclass` or `& + .additionalclass` or whatever you want.
So now we discuss web standards on chrome.com ?
This looks like the beginning of the end of a free web governed standards established by an independent body. I would have expected such an article to be published on w3.org...
It also looks like asking the general public for a go to make CSS even more complicated, when we all know it's a one-way ticket to Chrome dominance, ie a rhetoric dictators have been using. Note the absence of the option to "do nothing" and keep good-damn CSS as it is already after 25+ years of tinkering that haven't solved its problems.
I mean even one of the long-term editors of the CSS spec has raised hands and warned against including ever more inessential and author-oriented features [1]. That was almost 14 years ago.
Variables (since they are not just constants), flex and grid all allow doing things that were previously not possible or at least not reasonably. Nesting is a pure syntax change that is already solved via a preprocessing step. So yes, we should weight what this adds in convenience vs. the cost of the adding this to all browsers, maintaining support for it forwever, the impact it has on future additions as well as new websites using this not working on older browsers. Note the should - I'm perfectly aware that the web dev ecosystem already often ignores many of these.
Scoping and nesting are two things that would solve 99% of problems when it comes to organising CSS (and components). They should definitely be on the browser level and not "solved by preprocessors".
I disagree. Given the enormous complexity browsers have aggregated already, we should strive to make browsers "player apps" (like MP3 players): a relatively simple front end with huge versatility in server apps and back ends, rather than, like, everything for everybody. I'm aware that ship has sailed a long time ago, but piling ever more not-so-well-motivated stuff onto browsers just means the next generation has to start over with a clean slate; actually I think this is inevitable already.
CSS nesting is clearly serving the use case of components in web apps, as opposed to mere content-driven web sites. IMO a sensible approach would be to give web app developers powerful ways to tie into rendering and layout programmatically (a la Houdini API) when those apps will require JavaScript, and in many cases use CSS-in-JS anyway, rather than burden browser cores with ever more questionable CSS features.
"Would solve 99% of use cases" also sounds like famous last words ;)
> a relatively simple front end with huge versatility in server apps and back ends
Define "relatively simple"?
Nesting and scoping in CSS don't make frontend any more complex. CSS has had problems with it's flat top-level structure since the very beginning of its existence. Approaches like BEM didn't appear out of thin air.
> CSS nesting is clearly serving the use case of components in web apps, as opposed to mere content-driven web sites.
Even a content-driven web site has a plethora of repeating and/or complex elements that are a pain to define and maintain.
> Would solve 99% of use cases" also sounds like famous last words
Google has poured hundreds of millions of dollars into web components whose primary use case (that is, what they are being used for in reality) could've been solved by scoping and nesting.
Casacading alpplies to all elements in the cascade. Whether you want it or not.
At the same time it's impossible to say "hey, I want to inherit from that particular style", and not have the cascade apply to it. For example, I want a button and I don't want anything in the hierarchy above overriding its borders, or colors, or fonts, or...
Oh, I see. Arbitrary non hierarchical opt-in inheritance. Or perhaps a cascading inheritance opt-out, and you could add classes back as you please. That would be cool.
Not sure how it would impact browser parsing and drawing, but it sure would be useful.
Those are constants. CSS variables are more powerful than that since they can be changed at runtime which allows e.g. minesweeper in pure HTML and CSS: https://github.com/propjockey/css-sweeper
Nobody's saying it can't be done. They're simply questioning whether it should be done, since we have tools that solve this problem already. It's not giving us anything we don't already have, and so the value proposition in making the syntax more complex is pretty shaky.
Why should the tool be necessary? I'd much rather write plain CSS than use Sass, and nesting makes that significantly more readable and easier to maintain.
Whether the tool should or should not be necessary is an entirely different question. The reason it is necessary is because it enabled writing "CSS" in a way that it was never meant to be written. And as evidenced by the blog post and all of the discussion here, adding the functionality to browsers isn't at all straightforward.
So a better question is why shouldn't the tool be necessary? It's all about writing the same CSS in a more developer-friendly way, not enabling CSS to do something it never could before (like variables and flex did). Why should browsers have to worry about this added complexity? Just write your stylesheets however you'd like, and then run a tool to compile them down to standard CSS.
> asking the general public ... when we all know it's a one-way ticket ... a rhetoric dictators have been using. Note the absence of the option to "do nothing"
regrettably reminiscent of the Wikimedia Foundation
I agree it shouldn't be on chrome's website, but nesting in native CSS is a very welcome and needed change, and one of the last things I consistently reach for SCSS for. It's been requested for years.
It does have the ability to be a foot-shotgun, but so do 3/4s of the features in CSS if you don't understand them. And people that want it would probably just do the same thing in SCSS anyways.
Things don't get a lot of eyeballs on them on w3.org which defeats the object of the post which is to get developer feedback. It's pretty normal for CSSWG members to ask for opinions via tweets, on their own blogs, or in other places. I've posted stuff to my personal blog in the past. This is something that will be worked on in the CSS Working Group, it happens to have people from Chrome working on it, however it will be discussed with the rest of the CSSWG who have a range of affiliations: https://www.w3.org/Style/CSS/members.en.php3
The request for feedback could have been discussed at w3, and google could still have made a blog post about it saying something like "this is what we have been discussing at w3 so far, your feedback is welcome on w3.org"...
The post is on chrome.com and mentions the decision makers as "we".
nesting will be nice when it lands, but i'm most excited by far for :has() support. i know chrome and safari have initial implementations in place and firefox just recently started. :has() will be the biggest advancement in css since grid, moreso than nesting or container queries, because of the power it puts in the stylists' hands. unfortunately trying to track its progress is a haphazard gander into an opaque maze of different sites and bug trackers.
I too am salty about this, but the W3C uses IRC for discussions and have no easy forum or boards for RFCs (like TC39 with their use of GitHub for ECMAScript proposals).
Contributing to web standards is not really accessible and that contributes to this Chromium-centric world.
Where would one raise a proposal for something like adding a `sandbox` flag to `<script>` elements, the proposal of adding a feature like a "Context Menu API" for PWAs or discussions around pro-privacy features?
IMO this is one of the things, like TypeScript, where transpiling before hitting the browser is good enough considering the downsides of baking this into three web engines.
Languages that transpile to CSS require extra tooling and setup, and if there's one thing our web apps have too much of it's tooling and setup. I will be able to make much stronger cases to get rid of SASS, SCSS etc when this happens.
x = .nesting {
color: hotpink;
}
y = x > .is {
color: rebeccapurple;
}
y > .awesome {
color: deeppink;
}
plus:
x = .nesting;
y = x > .is;
z = y > .awesome;
x {
color: hotpink;
}
y {
color: rebeccapurple;
}
z {
color: deeppink;
}
The names x, y and z aren't ambiguous because they are lexically defined. They could be allowed to shadow tags:
pre = .foo > .bar;
then from that point on, pre isn't the HTML tag, but the above definition. Or else tags could be reserved: cannot use those as names. (Causing trouble when new tags get introduced; bad idea.)
Regarding the @nest syntax, why couldn't it look like an attribute?
Why use containment to express the nesting? What is being nested is the selectors. The braces group something else: the attributes affected by selectors. Why would nested selectors have to go into those braces?
The + character at the start of a rule could mean "the selector of the previous rule":
Here, because the nested @stack follows a block of parallel rules, a Cartesian product semantics can come into effect. That is to say, it is equivalent to:
The mandatory : could be followed by required whitespace, making :: a syntax error.
Or the whitespace could be strongly recommended, with implementations encouraged to diagnose if it is missing, at least in ambiguous-looking situations like ::hover. (Is that a forgotten space? Or a forgotten colon?)
(or if it (1) means (2) then it is unable to represent the A below)
In other words you can write
<div>Hi<span>there</span></div>
And using the first one get a red there on hover (A). But the second one intends to get a purple Hi there on hover (B). But You can't express that because the mandatory space clashes with the CSS (implicit) descendent combinator (space).
Honestly, if SCSS works, then I don’t see how they can say it’ll be a problem in the browser. It already works, lets not require everyone to redo everything they already have.
Have the browser show a warning in the style of “An unbounded lookahead is making this page slow” when, and if it happens, and the site owner can do something about that crazy thing.
That’s already true for all the Javascript tracking and ads. It seems silly to now worry about a few extra ms that it takes the CSS parser to resolve it’s difficulties.
Maybe if they specified how long this ‘infinite’ lookahead takes in all their scenarios, it would be more compelling.
Tracking and ads scripts aren't special here. Yes page rendering is paid by the browser every time, and this group of people is specifically worried about the CSS part of that process. They're separate concerns and don't need to influence each other design wise
That feels like a very user-hostile point of view. It's better to spend a bit of time to get this right rather than just doing it in a way that affects millions of web users.
Yes but that cat is so far out of the bag that it's meaningless. The browser's model is "ship source code, and we compile and run it." I would love a universe where it would be possible to ship some binary artifact to browsers so that it can load it as if it was an already opened page, complete with local state and skip all the parsing/compiling steps.
Meh - every step down that road has been fraught with significant issues. See: Flash or Silverlight, for example. (Yes, I know, not quite the same as if the browser did it natively.)
One of the beautiful things about the web is the ability to jump into the dev tools and look at the code. I know it's harder than ever to actually learn from that now, and I predict you are correct that someday we'll get to the point where it's a binary blob delivered. But I'm not holding my breath waiting for it and certainly not advocating for it. I'll adapt when/if it happens, but I feel a bit of the "magic" of the web will die that day.
What's the real performance cost of the lookahead on an average/decently written page?
Terrible philosophy to base the design on implementation details of the engine. Unless it can be shown that performance will degrade significantly for the average page, which I suspect not
I don’t know that I’d call this an implementation detail of the engine.
As I read this, the spec would force the implementation on every engine - and there is no known algorithm that could answer the question in constant time given the spec.
Infinite look ahead means infinite look ahead. Which means (IIUC), every time this case is encountered, any/every CSS engine (existing today or yet to be built - every CSS engine from this point forward) would have to potentially check every subsequent token until it could properly understand the context of the symbol.
Given that - I don’t like the idea of any CSS file being able to put my browser into that state.
Correct me if I'm wrong, but my interpretation of the lookahead is that performance only degrades once CSS becomes deeply nested.
That the need to lookahead only applies to syntax nested within another block.
It's not a realistic use case for somebody to nest CSS more than a few levels deep, and if the lookahead is a known part of the spec it would become a best practice not to excessively nest things.
So with side by side implementations, what's the cost in time/CPU of a realistically scoped CSS file with a realistic level of nesting? That's the answer I'd like to know.
Optimizing for an avoidable theoretical worst case shouldn't be a major consideration in design.
The net result of using a weird syntax for nesting is that developers will just keep using preprocessors forever because they're far more ergonomic. So all the spec saves is some built file size, while not improving anything about the development process
I learned a long time ago that this kind of approach only brings problems in the long run: you end up building house of cards all the time. All our problems went away when we adopted functional CSS.
Choosing only among the given proposals, what I see is one clear, concise and reasonably elegant syntax, and two ugly and noisy ones. Frankly it vaguely feels like the two latter options are there just to shepherd the ‘community’ (if there's such a thing) toward the least egregious variant and offer a semblance of discussion. It's not even necessarily bad that there's one obviously best option, if it's dictated by technical constraints—just, why bother me with the other two?
If they're seriously considering the latter two options for us to be stuck with, that's very concerning, and I'll be nervous about the whole affair until something definite rolls out.
Of course, it might be that the syntax authors foresaw some future problems that I don't, but their arguments aren't provided.
In fact, I don't even see why the `@nest` marker is necessary at all. The W3C draft says, “Some valid nesting selectors, like `.foo &`, are disallowed”—but why? They introduced the `&` marker to tell nesting rules apart from anything else, but then it turns out that they can't do that with the marker either? See `&` anywhere in the selector, realize it's a nesting—how much lookahead is needed for this? `&` can't be in a property name or media rule, can it? Surely there's a reason for this constraint, only I can't figure it out—granted, I barely know anything about implementing a CSS parser.
(Update: text cited by @pointlessone notes that a nest-selector might look like `color:hover & something`, which is indeed quite wacky and apparently befuddles the parser.)
Also, philosophically, a more-verbose marker like `@nest` would help avoid the growth of ascii salad in the syntax (the way that Python and Lisp avoid it, compared to Bash and Perl)—specifically if the marker was used instead of both `@nest` and `&` together in those proposals. However, it's probably not such an issue in regard to a feature that's gonna get plenty of exercise and is practically core functionality.
The post makes it quite clear that all three proposals sprinkle some markers and constrain their use to avoid ambiguity for the parser, so not sure what you're talking about.
Regardless of the merits and feasibility of implicitness vs. explicitness, I seriously question whether it's ever a good idea to mix the two. That's just asking for confusion exactly in those cases where selectors are confusing already; i.e. adding complexity where it hurts most.
Take a better look at example 5: [1]. In that case, the `@nest` marker is necessary for proposal 1. Proposal 2 is saying, if we need @nest some times, why not always make it required, for consistency. In that example, I'd say proposal 3 is the least ugly.
However, it's unclear to me why we want to allow the & marker to appear anywhere in a rule, and not just at the start. Every time I've used nesting in a CSS pre-processor, I've always added to the selector at the end.
The example by itself doesn't explain why I can easily grok that `.parent &` is a selector with nesting, but the parser can't. The `&` marker is introduced for nesting in the first place, so adding another one to specify that it's definitely hundred-percent pinky-swear artisanal nesting looks plain dumb.
I got a reply asking why this is, which either got deleted or isn't showing up for some reason. The reason is, before nesting, you know based on context whether something is a selector or a declaration. Top level / inside a media query, it's a selector. Inside a selector, it's a declaration.
With nesting, but without the prefixed @nest, you have backtrack after arbitrarily many tokens if you see a & and need to switch from parsing a declaration to parsing a selector.
E.g.:
div {
div:hover div /* 4kb of div */ & { }
}
Note that the colon makes it a valid-looking declaration (property `div` and value `hover div...`), until you read all the way up to the &.
Yeah, it's all quite simple when thinking for a bit on a clear head. For some reason I didn't realize at first that the whole ‘:hover’ problem only pops up when nesting is introduced.
> if we need @nest some times, why not always make it required, for consistency
Prefixing a parent selector seems super ugly. The facility is available in SCSS, but I suspect it is not used much (e.g. comment above not knowing the feature existed).
Using & for the usual case of appending selectors seems sensible to me.
Aside: I suspect using & to avoid lookahead parsing is important for efficiency and to avoid DoSing the browser (especially malevolently).
Though then again, I still don't see why the parser can understand that `color:hover something` is a selector, but can't understand that `color:hover & something` is a selector with nesting. I don't need to mark a shitty selector like `color:hover` with special markers for shitty selectors, do I?
Update: it finally dawned on me that only with nesting things like `color:hover` can appear where both properties and selectors are expected. Apparently today is not my thinking day.
Still would like to understand exactly how ‘unbounded lookahead’ arises.
Lookahead is how many tokens the parser needs to see before it can make a decision what branch it is in and start ingesting those tokens properly.
In `& color:hover` you've got at least four tokens (making some basic assumptions about how the tokenizer works and ignoring whitespace):
1 &
2 color
3 :
4 hover
In this case you've got that `&` in the first token and the parser knows what to do with that (only appears in nesting scopes). (No lookahead needed.)
In the case of `color:hover &` the situation reverses:
1 color
2 :
3 hover
4 &
Tokens 1/2/3 are ambiguous (could be a property:value or could be tag selector:state) and it isn't until token 4 that it becomes obvious that it must be a nested selector because that & token is the disambiguator. So here you need a lookahead of 4 tokens just to disambiguate.
The problem is "unbounded" because the way selectors and values work they can be arbitrarily long. You can have "long property values". This doesn't make a lot of sense in current CSS, but is a valid property value assignment:
color:hover, tt, .frankenstein, p;
This is a possible (weird) nesting:
color:hover, tt, .frankenstein, p & { }
The number of tokens needed to lookahead is "unbounded" because you can keep adding tokens to that weird property value or to the nesting example with no clear limit.
Honestly I think "@nest always" makes the most since since it would be consistent with @media and unmagic the leading '&'. It gives @nest the feel of printf.
yah, i have a strong preference for the first option, and don't mind that it requires using an & token relative to scss that requires none.
the @nest always option seems overly verbose, and adding more brackets visually overloads them in a way that makes it harder to parse quickly when scanning.
I think option 1 is the most elegant, but I also realize that as a user of Less and Sass it is the most natural choice for me. The `@nest` having to appear when the ampersand exists after any other character in the line is an okay inconsistency for me, since I rarely come across that form and when I do it it's in short bursts with tightly coupled selectors. You can also think of it as a warning sign for yourself that you have an ampersand in a not-so-conventional spot in the line. If option 2 happens I wouldn't be disappointed, either. The brackets example, however, just looks wrong to me.
I also found the second and third options unnecessarily noisy. Though, I'm not surprised with the obvious syntax warts being proposed considering the disaster that is `globalThis`.
I do hope the first choice is the one used as it is the most reasonable while working around existing CSS parsing rules.
To be honest, I just don't care anymore about plain CSS, it is just an intermediate representation to me. This feels like bikeshedding over the syntax for LLVM IR.
Just add SCSS to the web inspector that's the only place where I'm still exposed to plain CSS.
Wildly different experience in that I'm back to writing plain CSS on many simple to medium complexity projects because these tools aren't providing me enough value for the complexity and dependencies they bring. I've not written Sass or SCSS in about 4 years now, and when I do bring a tool, it's a countable number of PostCSS modules to bridge some gaps for features that haven't yet been upstreamed to all browsers.
If i read this right, the only reason we can't just use & anywhere in the selector without some funky @nest indicator, is that browser vendors don't want their css parser to have unbounded lookahead just to figure out if something is a selector or a property.
I gotta say, this strikes me as rather developer hostile. Now, developers have to appease a crippled parser by injecting @nest (or a billion brackets) strategically. The amount of devs that are going to have to scratch their heads at each of these three weird proposals is way more than the amount of devs building CSS parsers, so I think they don't have their priorities straight.
I mean, in the 99.9% case, nested selectors are going to be at most some reasonable amount of tokens long. I understand wanting to avoid too many heap allocations but if that's the only issue, would it be that big a hack to first try to parse it with a bounded (but reasonably long) lookahead, and just reparse the whole thing in "insane CSS" mode with mallocs all over the place if the bounded one errors out? That way virtually all sites keep the fast small CSS parser and if some web dev really much challenge the engine with obscene code, it's supported but slow. They do this all over the JS engine, why can't CSS get a teeny tiny little bit of that attitude if it means CSS stays easy (enough) to learn?
Stuff like that is, however, complex. Complexity breeds both slowness, expense to implement, and security risks (at the very least DoS risks). It may also require a pretty significant rearchitecture of existing css parsers, which will hinder adoption. It also makes future competing browser engines that little bit less likely.
There's a bit of tradeoff here of devs vs. users.
But regardless, I think the implicit syntax (as is) is clearly the worst anyhow - because it's important even for devs to avoid syntax with gotcha cases. Unfortunately the optional/implicit style can't always avoid @nest, and precisely in more complex cases it is required. That makes those even more confusing; I'm not a fan of adding complexity precisely where it hurts most, even if it's a little shorter in some cases.
Had the implicit syntax _always_ allowed omitting @nest it'd be a different story, that's clearly best, but if browser-manufacturer's refuse that option, well, then let's not make it optional only in some cases.
Personally, I'm not sold that this is a great feature at all, because nesting encourages high selector specificity, and tightly coupled details of the dom structure with css. That's still a recipe for specificity battles and refactoring gotchas, no matter the syntax. Nesting is a dangerous feature because it's pretty attractive superficially, but leads to pain way later in the development process: not good.
In some niche cases (e.g. for pseudo-elements or media queries) there's never a maintainability problem with nesting, and hey, those are some of the cases where there's no parser ambiguity either, even with the implicit nesting syntax! I'd rather CSS restricted the nesting feature to those sane choices, than repeat SCSS's mistakes.
249 comments
[ 5.3 ms ] story [ 265 ms ] thread> Nesting style rules naively inside of other style rules is, unfortunately, ambiguous—the syntax of a selector overlaps with the syntax of a declaration..
> It is, in most cases, eventually possible to tell properties and selectors apart, but doing so requires unbounded lookahead in the parser; that is, the parser might have to hold onto an unknown amount of content before it can tell which way it’s supposed to be interpreting it.
> CSS to date requires only a small, known amount of lookahead in its parsing, which allows for more efficient parsing algorithms, so unbounded lookahead is generally considered unacceptable among browser implementations of CSS.
https://drafts.csswg.org/css-nesting/#nesting
Honestly tho, I think scss has it right where ampersand is only used for grouping / parenting.
It's feasible when you're running a preprocessor on your own code one time, but not feasible for the browser to do on unknown code on every stylesheet it encounters.
But finding 3 other solutions (because of "performance"), not promoting them through transpiling, and then forcing them on web devs seems... googley. Not the first improvement they forced down our throats and unfortunately not the last. /rant
[1] https://twitter.com/jaffathecake/status/1552200992179077126 [2] https://developer.chrome.com/blog/help-css-nesting/#:~:text=...
The 3rd party CSS tooling can pick whatever syntax it wants because it is starting on a green field. Browser probably can't?
Otherwise just use sass, it is well-liked and proven. Or, as you suggest, do nothing.
Because time and again userland implementations of useful functionality are better, more ergonomic and practical than anything committees come up with.
- the nested CSS proposal supports everything the SCSS syntax does, plus some potentially useful combinations
- the SCSS syntax would literally lead to a worse user experience, both for the end user whose browsing performance is now degraded, and anyone implementing parsers for CSS (since the infinite lookahead is quite a bit more complicated)
- the committee isn't just "coming up" with the new syntax, they are asking for feedback. Let's not pretend this is the same as a committee deciding something without taking input from the affected users.
I understand that generally a de facto standard will be more useful than a de jure one. But this isn't some committee coming up with their own convoluted version of a standard because of NIH - they are communicating clearly and openly around why the established standard would be problematic. Shouldn't we as a technical community try to find the best solution for the problems we face, instead of taking principled stands and ignoring the technical hurdles in our way?
If you resolve the ambiguity in real time in favor of the preprocessor syntax, you break existing websites.
If you perform a preprocessing step it now blocks CSS parsing and slows down all websites on the internet.
Maybe the preprocessor authors should have thought harder about their syntax if they wanted it to be adopted as a web standard.
All of them are atrocious to type and parse for a human.
The option 1 is probably the best but urgh
I hope the proposal fails entirely, and that no nesting syntax is adopted. It's not worth it, and nesting in SCSS is abused more often than not anyhow.
There are a few exceptions like pseudo-elements and media queries that are always harmless, but most nesting is just asking for specificity gotchas later, or component refactoring surprises.
Gotta love standards bodies!
Once it’s out there, it has to be supported for a long time. It’s worth deliberating to get right. Nobody will die because they took their time.
It’s scss.
No point in making a poll with an answer that’s already decided.
Put another way: inconveniencing developers is preferable to inconveniencing users.
It looks like somebody decided that nesting support must somehow support "reverse nesting", eg. that you'd like to define what happens to .foo when it's inside a .bar, but nested inside the definition for .foo:
.foo { .bar > .foo { ... } }
Does anybody actually want this? It feels counter-intuitive and weird, and with the syntaxes proposed, seems to provides little benefit compared to just doing it as the old ".bar > .foo" without nesting.
If you drop the requirement for nesting to work in reverse, you can just go with the proven SCSS syntax which is extremely clear, straightforward to use, and requires 0 extra characters typed.
EDIT: Apparently the '&' or some other special token is required to prevent the need for unlimited lookahead while parsing. This is actually explained behind a folded caption in the article, which I initially missed.
I agree that this syntax could be convenient, though I'm actually a fan of having all the "@media" changes in one place, which is what the current syntax forces.
I assume that for the same reason that they cannot support direct nesting with no extra tokens (parsing issues), they also cannot support the simple SCSS version and instead went for the "@nest .b & {" mouthful.
.button { @nest div.small-print & {
a { @nest :not(nav) & {
Including the body:
.button { @nest body.has-js & {
FYI, this `:not(nav) a` doesn’t do what you almost certainly intended. It doesn’t match “an <a> that is not inside a <nav>”, but rather “an <a> that has at least one ancestor that is not a <nav>”. You can’t currently express what you wanted there; the best you can generally manage is if you can anchor it with a parent selector, e.g. `:not(nav) > a`, which will match any <a> whose immediate parent is not a <nav>. I can imagine something along the lines of `a:not(:has(nav :scope))` working in the future too.
The problem with nesting is that it is seductively easy to write, but encourages two coding issues that cause maintenance headaches later on. Firstly, it encourages high selector specificity, and that means potentially enjoying specificity battles later on. Secondly, it encourages tightly coupled DOM structure with CSS - and tight coupling makes later development more expensive.
Both of these issues are significantly less serious when using reverse nesting than forward nesting.
Forward nesting allows for expressing the concept of "parts of a component" by it's DOM structure, so when the component is tweaked, selectors that aren't explicitly tied to any part of a component suddenly break. But reverse nesting is more natural to use in the context of expressing a special case; i.e. my thingmabob is generally green, but in this specific context it's grey.
So on coupling, reverse nesting is less risky: whil it's not impossible to "compress" your css and thus introduce coupling between component structure and a reverse nested selector, it's not a very natural mistake to make.
And on specificity too, reverse nesting is less likely to cause surprises - after all, since you're expressing a more specific situation, you're almost always likely to want the nested selector to win that specificity battle.
So, I'd argue that reverse-nested selectors are actually one of the few general class of nested CSS selectors that are unlikely to cause maintenance headaches later on. Similarly harmless are nested media selectors (again, because nesting implies expressing an exception), and nested pseudo-elements (because there structure and CSS are intrinsically coupled an expressed within the CSS anyhow).
What you call "reverse nesting" is actually the only best good kind of nesting. QED?
For: `@nest .bar > & { ... } }` (just using the first syntax for simplicity here)
That doesn't translate to: `.foo { .bar > .foo { ... } }`
It's actually just: `.bar > .foo { ... } }`
Here's the relevant excerpt.
> ## Why can’t everything be directly nested?
> Nesting style rules naively inside of other style rules is, unfortunately, ambiguous—the syntax of a selector overlaps with the syntax of a declaration, so an implementation requires unbounded lookahead to tell whether a given bit of text is a declaration or the start of a style rule.
> For example, if a parser starts by seeing color:hover ..., it can’t tell whether that’s the color property (being set to an invalid value...) or a selector for a <color> element. It can’t even rely on looking for valid properties to tell the difference; this would cause parsing to depend on which properties the implementation supported, and could change over time.
> Requiring directly-nested style rules to use nest-prefixed selectors works around this problem—an & can never be part of a declaration, so the parser can immediately tell it’s going to be parsing a selector, and thus a nested style rule.
> Some non-browser implementations of nested rules do not impose this requirement. It is, in most cases, eventually possible to tell properties and selectors apart, but doing so requires unbounded lookahead in the parser; that is, the parser might have to hold onto an unknown amount of content before it can tell which way it’s supposed to be interpreting it. CSS to date requires only a small, known amount of lookahead in its parsing, which allows for more efficient parsing algorithms, so unbounded lookahead is generally considered unacceptable among browser implementations of CSS.
It still doesn't explain why all the proposals feel the need to be more powerful than SCSS nesting (allowing "reverse nested" definitions where the element being nested in is not at the start), but it does explain the need for "&".
EDIT: Actually, it looks like SCSS supports the same thing by using '&' anywhere in the definition. I'll just stop posting since it looks like my knowledge is too rusty to contribute positively.
If scss can do `.parent & {...}`, that would be the same as the proposal.
Edit: I put together (painfully typed out on a phone) an example regex[1] to use the color:hover example. It isn't perfect, but it also isnt boundless.
[1] https://regex101.com/r/aOc2Pz/1
Parsers know the current state and where to go next out of previous reductions and a few lookaheads which they can use at places known to be clear of ambiguity in a ~fixed number of lookaheads. Parsing something that diverges only down few pages of tokens is problematic size/speed/algo-wise, but the question is how much does that cost really and who should pay.
Edit: But honestly I think this is all wrong direction. Any technology, format, method has 1. features and 2. form. Form is irrelevant because you can make tools to convert any form into any. Features are important because you can’t add features by tooling. But $subj adds zero features, only a new form which is already feels like a compromise. Why do that then? We have to brush our tools instead, e.g. create default nginx-scss plugin, standalone scss caching servers etc. Then everyone could use extended syntax without resorting to all-in-one monstrosities like webpack.
This all seems like purely academic objection that in practice could be made equally fast.
Next { or ; is not few pages down
A little nuance is that when it is, we either want to make that illegal or to process this case anyway. Both ways open a next can of worms.
If I'm trying to find a local emergency room, I really don't want to have to waste time because a developer wrote (or some tool generated!) slow CSS.
I started seeing a lot of references to Chuck Norris.
After further digging, found that the entire test library suite was being added to code.
Site load time was halved after fixing.
color:hover .foo .bar .qux div span
You have no idea whether to parse that as a property (setting `color` to that string) or as a nested selector (a span below a div below .qux below .bar below .foo below a color element that's being hovered over). You only know that it's a property if you see a `;` or a `}`, or a nested declaration if you see a `{`.
So yeah, the ambiguity might not be resolved for a pretty long time. And, unless there's some aspect to CSS's grammar I'm not thinking of, this ambiguity exists for every single property (even for stuff like `background-color: blue` -- the parser shouldn't know that `background-color` isn't an HTML element or that `blue` isn't a selector).
It really wouldn't surprise me if adding unbounded lookahead to every single CSS property would cause a pretty significant parsing slow-down.
No, form is not irrelevant.
People don't write bug-free code. The form has to work reasonable well even when there is a syntax error in the code.
C++ templates is a main example where this often goes hairwire.
It's really less about benchmarking and more about managing risk: large unbounded lookahead in scss might crash a single concurrent pod in your CICD - isolated, detectable and really easily remedied. The same here could crash many users' browsers - not so easily fixed.
I feel like we're talking serious edge cases here.
I'm not sure I can think of a better explanation/examples than the gp. It essentially comes down to ambiguity between single element selector names (which are unprefixed in CSS - no dot or hash) and property keys (also plain identifiers).
The only similar nested braces form we have in current CSS is always @-prefixed. There's no unprefixed equivalent for parent blocks in current syntax (plus those blocks are currently limited to a single level which I guess mightt simplify things further given the prefixed selector is always at root level)
> I also don't see why a dramatic update like this couldn't also introduce / require a 'strict' style to be considered valid.
I think it could and I think the call for input is open to that as a possibility. The above comment is just referring to a fully compatible direct scss implementation of nesting.
Reposting my earlier painfully phone-typed regex to illustrate the point: https://regex101.com/r/aOc2Pz/1
Edit: I should add that I don't doubt that those involved know what they're talking about. I know nothing of briwser-level CSS parsing. I am certain it is a nightmare. I would really just like a better example to explain the unbounded aspect. Especially when it is being given as the primary excuse to cripple / greatly obtusify a syntax I'm being asked for an opinion on. Maybe it's just me but I would expect that an evolution of this magnitude would afford a bit more compromise elsewhere.
The advantage of only looking ahead by a constant is that parsing can be done in linear time. For a backtracking parser, you have to look ahead by up to n characters each time, leading to a quadratic runtime in the worst case.
[0] https://en.m.wikipedia.org/wiki/LR_parser
Seriously tho it seems like a pretty major feature to be introducing. Why not do it right? Surely the complexity should be concealed rather than out front being dealt with in user space.
Plus, different ways of implementing a parser could have different ways of resolving ambiguities. Maybe parsing `color:hover {}` as a selector happens to be super easy in Firefox's existing parser, but would require a rewrite of Chromium's parser, for example.
SCSS apporach, for instance, requires look ahead.
Consider: color:hover over some random markup &
Currently CSS can be sure whether that is a selector or a property depending where it finds it. If it's in a ruleset or at-rule block, it's a selector. If it's in a declaration list, it's a property-value pair.
Now, with nesting it becomes confusing. Parsers can not rely on the state to decide what they're looking at.
One way to fix this is, like many suggested here, to require look-ahead from parser. Which can be a solution in a well defined environment. Unfortunately, the Web is not one of them. We have many underpowered kiosk-type devices that probably can not spare arbitrarily large look-ahead buffers.
Another solution is to somehow let the parser know what it's looking at. That's why leading & is a good indicator it's a selector. & is not used for anything else in CSS. But for selectors where it's not first we need @nest (or anything else that unambiguously marks a selector).
Look-ahead requirements are bad for another reason, too. It makes CSS platform-dependent. Currently any version of CSS clearly states what it provides. Implementations can be partial but they can not claim they support CSS 2.1, for example, when they don't implement 100% of it. Wit look-aheads things become murkier. An implementation can support 100% of the spec but depending on the look-ahead required to parse a stylesheet they might not work. Currently, I don't think there are any CSS features that depend on platform limitations, at least not for parsing. Requiring a look-ahead buffer to parse a stylesheet can be very problematic. Imagine, Twitter got hacked and their embeds started stylesheet have a couple selectors that require 1M tokens look-ahead buffer. How that would break every site that embeds tweets?
Basically do it by multithreaded recursive descent.
Then what on the left of { is a selector and what's on the right are the properties.
What you could conceivably do is to have a very simple parser whose only job is to have just enough understanding of CSS to be able to split the CSS document into chunks, and then have a proper parser which works in parallel on the chunks produced by the first parser. I can't imagine this would be faster on most code though, considering how simple the full CSS parser is today, and it still requires a single thread which scans through and parses the entire CSS document.
Regardless, browsers are already really multi-threaded. When you parallelize parsing in this way, even if parsing gets a bit faster in isolation, you're increasing the amount of computation necessary to parse the CSS (i.e spending 1 second on one core is less computational work than spending 0.5 seconds on 4 cores). Your parallel CSS parsing will be taking cycles away from HTML parsing and javascript execution.
[1] https://caniuse.com/url-scroll-to-text-fragment
https://www.w3.org/TR/css-nesting-1/#nesting
The W3C document template even kindly provides a quick way to copy those links to every header using the section mark (§).
[1] https://developer.mozilla.org/en-US/docs/Web/HTML/Element/de... [2] https://caniuse.com/details
https://www.w3.org/TR/css-nesting-1/#:~:text=Why%20can%E2%80...
I mean even one of the long-term editors of the CSS spec has raised hands and warned against including ever more inessential and author-oriented features [1]. That was almost 14 years ago.
[1]: https://www.w3.org/People/Bos/CSS-variables
Now that's a page that can be dated by its look.
> - Half the files is less then 7 lines. > - 90% is less than 163 lines. > - Only 0.6% is longer than 500 lines.
This part jumped out at me. Those were the days...
Nesting is way overdue. But of course we shouldn’t be discussing it at Google’s site.
CSS nesting is clearly serving the use case of components in web apps, as opposed to mere content-driven web sites. IMO a sensible approach would be to give web app developers powerful ways to tie into rendering and layout programmatically (a la Houdini API) when those apps will require JavaScript, and in many cases use CSS-in-JS anyway, rather than burden browser cores with ever more questionable CSS features.
"Would solve 99% of use cases" also sounds like famous last words ;)
Define "relatively simple"?
Nesting and scoping in CSS don't make frontend any more complex. CSS has had problems with it's flat top-level structure since the very beginning of its existence. Approaches like BEM didn't appear out of thin air.
> CSS nesting is clearly serving the use case of components in web apps, as opposed to mere content-driven web sites.
Even a content-driven web site has a plethora of repeating and/or complex elements that are a pain to define and maintain.
> Would solve 99% of use cases" also sounds like famous last words
Google has poured hundreds of millions of dollars into web components whose primary use case (that is, what they are being used for in reality) could've been solved by scoping and nesting.
Same goes for all the CSS-in-JS abominations.
It’s not scoping in the inheritance sense. The C in CSS is for cascading. Inheriting is the natural way to be for CSS
Unless we’re using different definitions of the term
At the same time it's impossible to say "hey, I want to inherit from that particular style", and not have the cascade apply to it. For example, I want a button and I don't want anything in the hierarchy above overriding its borders, or colors, or fonts, or...
Not sure how it would impact browser parsing and drawing, but it sure would be useful.
But true support is convenient. One of the best places to write CSS is the browser’s dev tools. Having CSS variables work there is awesome.
If it can be parsed almost as fast, I say go for it.
So a better question is why shouldn't the tool be necessary? It's all about writing the same CSS in a more developer-friendly way, not enabling CSS to do something it never could before (like variables and flex did). Why should browsers have to worry about this added complexity? Just write your stylesheets however you'd like, and then run a tool to compile them down to standard CSS.
regrettably reminiscent of the Wikimedia Foundation
It does have the ability to be a foot-shotgun, but so do 3/4s of the features in CSS if you don't understand them. And people that want it would probably just do the same thing in SCSS anyways.
The post is on chrome.com and mentions the decision makers as "we".
Also, the post ackshually refers to a W3C draft at https://www.w3.org/TR/css-nesting-1/
Contributing to web standards is not really accessible and that contributes to this Chromium-centric world.
You are very welcome to contribute!
How so? AFAICT if you want to contribute just go contribute. It's open to all. Discussions are had on public mailing lists, github, etc...
Here's one place to start
https://github.com/w3c
The end sailed us by a few years ago.
Chrome now dominates all standards bodies and pushes quite a few of its proposals forwards with utter disregard for anyone.
Didn’t they mean “implemented in Chrome and therefore forced into the CSS standard due to browser monoculture”?
Languages that transpile to CSS require extra tooling and setup, and if there's one thing our web apps have too much of it's tooling and setup. I will be able to make much stronger cases to get rid of SASS, SCSS etc when this happens.
Regarding the @nest syntax, why couldn't it look like an attribute?
Or just the colon: Why use containment to express the nesting? What is being nested is the selectors. The braces group something else: the attributes affected by selectors. Why would nested selectors have to go into those braces?The + character at the start of a rule could mean "the selector of the previous rule":
Or use a master outer brace for this: The understanding is that the rules grouped in the brace have a cumulative selector, rather than independent selectors.Or what if braces just group, and @nest (or whatever) is required to indicate the selector stacking semantics:
Now you can have N levels of selector refinement without N levels of syntactic containment. The containment is still available: Here, because the nested @stack follows a block of parallel rules, a Cartesian product semantics can come into effect. That is to say, it is equivalent to: The @stack following a parallel rule causes the @stack to distribute into the parallel branches.Kind of like Bash brace expansion:
Like
Or the whitespace could be strongly recommended, with implementations encouraged to diagnose if it is missing, at least in ambiguous-looking situations like ::hover. (Is that a forgotten space? Or a forgotten colon?)
You have a space in:
So just for consistency, you want it here:In other words you can write
And using the first one get a red there on hover (A). But the second one intends to get a purple Hi there on hover (B). But You can't express that because the mandatory space clashes with the CSS (implicit) descendent combinator (space).Have the browser show a warning in the style of “An unbounded lookahead is making this page slow” when, and if it happens, and the site owner can do something about that crazy thing.
Maybe if they specified how long this ‘infinite’ lookahead takes in all their scenarios, it would be more compelling.
One of the beautiful things about the web is the ability to jump into the dev tools and look at the code. I know it's harder than ever to actually learn from that now, and I predict you are correct that someday we'll get to the point where it's a binary blob delivered. But I'm not holding my breath waiting for it and certainly not advocating for it. I'll adapt when/if it happens, but I feel a bit of the "magic" of the web will die that day.
What's the real performance cost of the lookahead on an average/decently written page?
Terrible philosophy to base the design on implementation details of the engine. Unless it can be shown that performance will degrade significantly for the average page, which I suspect not
As I read this, the spec would force the implementation on every engine - and there is no known algorithm that could answer the question in constant time given the spec.
Infinite look ahead means infinite look ahead. Which means (IIUC), every time this case is encountered, any/every CSS engine (existing today or yet to be built - every CSS engine from this point forward) would have to potentially check every subsequent token until it could properly understand the context of the symbol.
Given that - I don’t like the idea of any CSS file being able to put my browser into that state.
That the need to lookahead only applies to syntax nested within another block.
It's not a realistic use case for somebody to nest CSS more than a few levels deep, and if the lookahead is a known part of the spec it would become a best practice not to excessively nest things.
So with side by side implementations, what's the cost in time/CPU of a realistically scoped CSS file with a realistic level of nesting? That's the answer I'd like to know.
Optimizing for an avoidable theoretical worst case shouldn't be a major consideration in design.
The net result of using a weird syntax for nesting is that developers will just keep using preprocessors forever because they're far more ergonomic. So all the spec saves is some built file size, while not improving anything about the development process
The lookahead only need to be applied to nested selectors.
But you can't know whether something is a nested selector until AFTER you applied the lookahead.
If they're seriously considering the latter two options for us to be stuck with, that's very concerning, and I'll be nervous about the whole affair until something definite rolls out.
Of course, it might be that the syntax authors foresaw some future problems that I don't, but their arguments aren't provided.
In fact, I don't even see why the `@nest` marker is necessary at all. The W3C draft says, “Some valid nesting selectors, like `.foo &`, are disallowed”—but why? They introduced the `&` marker to tell nesting rules apart from anything else, but then it turns out that they can't do that with the marker either? See `&` anywhere in the selector, realize it's a nesting—how much lookahead is needed for this? `&` can't be in a property name or media rule, can it? Surely there's a reason for this constraint, only I can't figure it out—granted, I barely know anything about implementing a CSS parser.
(Update: text cited by @pointlessone notes that a nest-selector might look like `color:hover & something`, which is indeed quite wacky and apparently befuddles the parser.)
Also, philosophically, a more-verbose marker like `@nest` would help avoid the growth of ascii salad in the syntax (the way that Python and Lisp avoid it, compared to Bash and Perl)—specifically if the marker was used instead of both `@nest` and `&` together in those proposals. However, it's probably not such an issue in regard to a feature that's gonna get plenty of exercise and is practically core functionality.
However, it's unclear to me why we want to allow the & marker to appear anywhere in a rule, and not just at the start. Every time I've used nesting in a CSS pre-processor, I've always added to the selector at the end.
[1] https://developer.chrome.com/blog/help-css-nesting/#example-...
With nesting, but without the prefixed @nest, you have backtrack after arbitrarily many tokens if you see a & and need to switch from parsing a declaration to parsing a selector.
E.g.:
Note that the colon makes it a valid-looking declaration (property `div` and value `hover div...`), until you read all the way up to the &.Prefixing a parent selector seems super ugly. The facility is available in SCSS, but I suspect it is not used much (e.g. comment above not knowing the feature existed).
Using & for the usual case of appending selectors seems sensible to me.
Aside: I suspect using & to avoid lookahead parsing is important for efficiency and to avoid DoSing the browser (especially malevolently).
Though then again, I still don't see why the parser can understand that `color:hover something` is a selector, but can't understand that `color:hover & something` is a selector with nesting. I don't need to mark a shitty selector like `color:hover` with special markers for shitty selectors, do I?
Still would like to understand exactly how ‘unbounded lookahead’ arises.
In `& color:hover` you've got at least four tokens (making some basic assumptions about how the tokenizer works and ignoring whitespace):
In this case you've got that `&` in the first token and the parser knows what to do with that (only appears in nesting scopes). (No lookahead needed.)In the case of `color:hover &` the situation reverses:
Tokens 1/2/3 are ambiguous (could be a property:value or could be tag selector:state) and it isn't until token 4 that it becomes obvious that it must be a nested selector because that & token is the disambiguator. So here you need a lookahead of 4 tokens just to disambiguate.The problem is "unbounded" because the way selectors and values work they can be arbitrarily long. You can have "long property values". This doesn't make a lot of sense in current CSS, but is a valid property value assignment:
This is a possible (weird) nesting: The number of tokens needed to lookahead is "unbounded" because you can keep adding tokens to that weird property value or to the nesting example with no clear limit.the @nest always option seems overly verbose, and adding more brackets visually overloads them in a way that makes it harder to parse quickly when scanning.
I do hope the first choice is the one used as it is the most reasonable while working around existing CSS parsing rules.
Just add SCSS to the web inspector that's the only place where I'm still exposed to plain CSS.
I gotta say, this strikes me as rather developer hostile. Now, developers have to appease a crippled parser by injecting @nest (or a billion brackets) strategically. The amount of devs that are going to have to scratch their heads at each of these three weird proposals is way more than the amount of devs building CSS parsers, so I think they don't have their priorities straight.
I mean, in the 99.9% case, nested selectors are going to be at most some reasonable amount of tokens long. I understand wanting to avoid too many heap allocations but if that's the only issue, would it be that big a hack to first try to parse it with a bounded (but reasonably long) lookahead, and just reparse the whole thing in "insane CSS" mode with mallocs all over the place if the bounded one errors out? That way virtually all sites keep the fast small CSS parser and if some web dev really much challenge the engine with obscene code, it's supported but slow. They do this all over the JS engine, why can't CSS get a teeny tiny little bit of that attitude if it means CSS stays easy (enough) to learn?
There's a bit of tradeoff here of devs vs. users.
But regardless, I think the implicit syntax (as is) is clearly the worst anyhow - because it's important even for devs to avoid syntax with gotcha cases. Unfortunately the optional/implicit style can't always avoid @nest, and precisely in more complex cases it is required. That makes those even more confusing; I'm not a fan of adding complexity precisely where it hurts most, even if it's a little shorter in some cases.
Had the implicit syntax _always_ allowed omitting @nest it'd be a different story, that's clearly best, but if browser-manufacturer's refuse that option, well, then let's not make it optional only in some cases.
Personally, I'm not sold that this is a great feature at all, because nesting encourages high selector specificity, and tightly coupled details of the dom structure with css. That's still a recipe for specificity battles and refactoring gotchas, no matter the syntax. Nesting is a dangerous feature because it's pretty attractive superficially, but leads to pain way later in the development process: not good.
In some niche cases (e.g. for pseudo-elements or media queries) there's never a maintainability problem with nesting, and hey, those are some of the cases where there's no parser ambiguity either, even with the implicit nesting syntax! I'd rather CSS restricted the nesting feature to those sane choices, than repeat SCSS's mistakes.