Help pick a syntax for CSS nesting
developer.chrome.com
developer.chrome.com
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.
dialog {
html:has(&[open]) {
overflow: hidden;
}
}
Is possible annd creates html:has(dialog[open]) {overflow: hidden;}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.
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.
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.
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...
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.:
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 &.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):
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. /* @nest always */
@nest & + .baz,
&.qux {
color: red;
}
It's clearer perhaps with different nesting, but that's fragile and a pain: /* @nest always */
@nest & + .baz,
&.qux {
color: red;
}
At least with option 1 you only end up with this issue in the uncommon case of multiple selectors and a non-leading &. /* @nest */
& + .baz,
&.qux {
color: red;
}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.
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.
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.
.foo {
display: grid;
@media (width => 30em) {
grid-auto-flow: column;
}
}
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.
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 { ... } }`
Edit: I still voted for `@nest` -- CCS is a pretty complex language for sure but I think it's terse expressiveness is why it's been successful.
Obviously `@nest always` and `brackets` are the same in disguise, and are here cause they're conceptually cleaner.
The problem with both of these is that they're unnecessarily verbose.
Why not have a shorter token than `@nest` and something that doesn't want more indenting (like `{}`)? For example `\`? And then if you want to make things a little less verbose, just assume the first character is going to be `&` cause that's going to be 99% of use cases.
And then you get something like this:
foo {
color: blah;
\ h2 { font-weight: bold }
\ h3 { font-weight: bolder }
\ i > & { ... }
}
But no one ever listens to me!I can understand how this would be useful in some situations. However, from a practical perspective, I've found that writing nested CSS ends up creating a tight coupling between DOM structure and style rules which makes everything more brittle and harder to refactor [1].
I've personally been spoiled by the scoped styles in Vue [2] which makes this style of isolation in CSS mostly unnecessary. It serves a similar purpose as related encapsulation ideas like shadow DOM, BEM, CSS modules, and styled components.
[1] https://csswizardry.com/2012/05/keep-your-css-selectors-shor...
(1 and 2:)
.foo {
color: red;
@nest .parent & {
color: blue;
}
}
(3:) .foo {
color: red;
{
.parent & {
color: blue;
}
}
}
(Without nesting:) .foo {
color: red;
}
.parent .foo {
color: blue;
}
The nested variants, for my sense, foreshadow grave technical debt – in specs, docs, browser code and CSS files.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.
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
.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.)[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.
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.
a {
&:hover {…}
&:parent(.theme):hover {…}
}
No crazy selectors with an ampersand in the middle allowed, but a way to apply a parent or ancestor selector, a bit like :where() or :is(). There could be :in() for ancestors.No one needs this monstrosity:
dialog {
@nest html:has(&[open]) {…}
}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.
Didn’t they mean “implemented in Chrome and therefore forced into the CSS standard due to browser monoculture”?
Most of the time I see nesting used, it ends up either in a stylesheet that is less readable than without it, or in generating css classes that are way more specific than necessary.
If you can't get the as-is SCSS syntax natively without a bunch of hacks, it shouldn't be done (a bummer, as I'd love to have that natively).
What could (should?) happen is codifying a pre-processor model into the standards track. Have a standard and then make some library the reference implementation.
Anyway, I also initially thought I preferred the brackets since it's explicit and some of the best wording is odd to me, but I think after considering my own `let rec` idea I prefer the optional `@nest` syntax because the only odd case which is relevant - and often enough that succinct but obvious syntax is warranted - is the child->parent->* case. That's the only time I'd want a heads up while visibly parsing css.
Honestly tho, I think scss has it right where ampersand is only used for grouping / parenting.
This as an afront to both democracy (uninformed decisions only, please!) and technical decision making.
I guess someone feels like the standards body is bikeshedding and wants to flip a "democracy coin".
"Technical Trumpism" but honestly worse.
It’s also not necessary - you can do just fine not nesting. If you have so many selectors on one rule that the nesting is really benefitting you, it’s very likely you have bigger problems.
They should just leave it to build tools.
That being said, if people really want this, then it definitely should not be something where the css file is going to be filled with "@nest" definitions.
What about allowing SCSS-like syntax through something like `<link rel="stylesheet" href="styles.css" allow="nest,foo,bar" />` which then tells the parser to react in certain ways - for example if there is a rule "color:hover", then do the usual CSS parsing, while "color:hover {" means that there is a nested rule.
That way the developer can opt-in to certain features, and you could even have it in the style tag with something like `<style allow="nest,foo,bar">...</style>`
But then you have an issue of custom features allowed through the tag definition, where chrome can force their way with things even more.
As far as having numerous `@nest` definitions, I don't know if your concern is file size, but if so, that really isn't an issue - assuming use of gzip (or some similar compression), there is almost zero difference in file size for repeated use of `@nest`. I did a bunch of testing related to multiple @media blocks versus a single one (a long time ago) and found it didn't make any significant difference in file size - and that adds significantly more characters in the uncompressed version than @nest does.
.has-nesting {
@nested (> .nested) {
/* rules */
}
}Gotta love standards bodies!
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.
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
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.
> 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.
Where? Is there reason Google moderating this? Are there options Google has not suggested?
Just add SCSS to the web inspector that's the only place where I'm still exposed to plain CSS.
> 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.
Then choose a bound! Make it big enough that it'll cover 99.9% of use cases, ignore any CSS rules beyond the bound with a console warning, and go on with life...
This seems so obvious that I feel I must be overlooking something. Perhaps the bound would be so big, that when parsing multiple stylesheets in parallel the memory use would be unacceptable?
color::hover { ... }
Or maybe class names can't be protected CSS properties. So `<div class="color">` would need to become invalid.Protected names are commonplace in programming. And I'd argue it leads to better software development by causing less confusion.
If they make it too verbose I'll just stick with SCSS. Honestly, I love vanilla CSS, but nesting is a huge need; it's the only reason I use SCSS to begin with.
I never missed nesting.
Don't Shadow DOM and components kind of make this not very important?
I've mainly seen nesting used in huge multi-kloc scss files that applied to the whole site, which I find a bad idea anyway.
I guess what I mean is: for small pieces of CSS it doesn't matter, and for huge nested structures.... you shouldn't do it
But instead of ampersand, let's use forward slash. It's very webby.
Now for this very fascinating article on the history of the ampersand
https://www.merriam-webster.com/words-at-play/the-history-of...
Ampersand has a long history, though much less popular, of being used to represent the source value of a text replacement (e.g. in Vim, :s/search/<&>/ will find “search” and replace it with “<search>”). This is pretty much exactly what’s going on here.
You might shoehorn yourself into an unideal outcome, trying to be backwards compat with the past. No?
I like the conceptual similarity of stepping through parts (like a URL) with stepping through parts of the selector.
Does it look good?
form {
margin: 1rem 0.5rem;
background: transparent;
/ fieldset {
border: none;
padding: 0;
background: white;
}
/ label {
color: darkslategray;
/ > input {
border: thin solid;
}
/ input {
border: none;
}
/ > button {
font-size: larger;
}
}
/ a {
text-decoration: none;
}
/ :is(a, input, button, label) {
color: inherit;
}
/:invalid {
color: red;
}
}
I think it looks really good actually. And I think the humble slash works really well as a stand in for a selector here.&&& are fucking noisy.
Maybe & are the way to go.
But...what about question mark? Question mark could be good. Or underscore.
form {
margin: 1rem 0.5rem;
background: transparent;
? fieldset {
border: none;
padding: 0;
background: white;
}
? label {
color: darkslategray;
? > input {
border: thin solid;
}
? input {
border: none;
}
? > button {
font-size: larger;
}
}
? a {
text-decoration: none;
}
? :is(a, input, button, label) {
color: inherit;
}
?:invalid {
color: red;
}
}
I think question mark's pretty good. It's the most semantic, visually for me. Maybe...I think.What about underscore?
form {
margin: 1rem 0.5rem;
background: transparent;
_ fieldset {
border: none;
padding: 0;
background: white;
}
_ label {
color: darkslategray;
_ > input {
border: thin solid;
}
_ input {
border: none;
}
_ > button {
font-size: larger;
}
}
_ a {
text-decoration: none;
}
_ :is(a, input, button, label) {
color: inherit;
}
_:invalid {
color: red;
}
}
Looks weird to me. But some people might like it.I don't really care which is chosen, I can learn to live with whatever.
I voted for `@nest all` because I don't think it's a feature I will EVER use, so I wanted it to be dumb obvious that someone is using it by explicitly putting @nest in the code. That way if I encounter it in the wild, I'll be less likely to confuse it with SCSS nesting, for example.
a { ... } a { ... }
a b { ... } a > b { ... }
is to is to
a { a > {
b { ... } b { ... }
} }
That's what I want. And it appears to fail correctly.The lookahead argument doesn't warrant poor syntax.
But we'll all still probably keep on using SCSS. So maybe something like `@nest` would be better to differentiate both, and the compiled result would still be a bit smaller.
I feel like nesting is going to take nice flat easy-to-read css and make it too complex and deep.
Reasoning: I do not see much sense in the use of additional brackets (option 3), since "&" already serves as a separator. I also do not see much sense in the use of the more verbose "@nest" (option 2), since conventional selector syntax uses single-character keywords already and there is no real parallel to things like "@media" or "@page" (as this isn't related to rendering and formats).
@nest .child {}
equals @nest & .child {} 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?
...sel... {
color: blue;
nest: > .whatever { ... }
}
Or just the colon: ...sel... {
color: blue;
: > .whatever { ... }
}
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":
.nesting { color: hotpink }
+ > .is { color: rebeccapurple }
+ > .awesome { color: deeppink }
Or use a master outer brace for this: {
.nesting { color: hotpink }
> .is { color: rebeccapurple }
> .awesome { color: deeppink }
}
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:
@stack {
.nesting { color: hotpink; }
> .is { color: rebeccapurple; }
> .awesome { color: deeppink; }
}
Now you can have N levels of selector refinement without N levels of syntactic containment. The containment is still available: @stack {
.nesting { color: hotpink; }
> .is { color: rebeccapurple; }
{
/* parallel rules: not a stack */
.awesome { color: deeppink; }
.terrific { color: yellow; }
}
@stack {
/* stack-in-stack, cross-producting. */
.even { color: red; }
.more { color: green; }
.nested { color: blue; }
}
}
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: @stack {
.nesting { color: hotpink; }
> .is { color: rebeccapurple; }
{
@stack {
.awesome { color: deeppink; }
.even { color: red; }
.more { color: green; }
.nested { color: blue; }
}
@stack {
.terrific { color: yellow; }
.even { color: red; }
.more { color: green; }
.nested { color: blue; }
}
}
}
The @stack following a parallel rule causes the @stack to distribute into the parallel branches.Kind of like Bash brace expansion:
$ echo nesting-is-{awesome,terrific}-even-more-nested
nesting-is-awesome-even-more-nested nesting-is-terrific-even-more-nested i {
font-variant: italic;
::hover {
color: pink;
}
}
Is that ::hover or : :hover? As in i::hover
Or i :hover
I think strictly speaking it is clear (it's the first one, as : is mandatory), but it doesn't look clear.Like
input {
border: 3000px groove purple;
color: transparent;
:::placeholder {
color: revert;
}
}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:
border: 3000px groove purple;
color: transparent;
So just for consistency, you want it here: /*empty prop name*/: ::placeholder {
color: revert;
} div {
color: blue;
: :hover {
color: red;
}
} /* (1) */
Is unable to represent div {
color: blue;
/:hover {
color: purple;
}
} /* (2) */
(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).Why do we need to add further complexity into the already complex thing that are browsers?
As the post mentions at the beginning, this can already be done with pre-processing. What do we gain by having more syntax sugar?
I don't think anyone involved in the standards-making is looking at how many bytes of CSS would be saved -- I don't think the number is likely to be significant to anything, and I don't think this is the motivation.
My understanding is that the intention is really to make CSS pre-processing less necessary, take features that people have found the need to have preprocessors for, and put them in the standard so the preprocessors aren't necessary for those features.
I feel like I've seen some of the undesirability of preprocessors in my experience with sass/scss, which is having to make backwards incompatible changes in order to avoid conflicts with the evolution of CSS underneath it. Also just having to setup the pre-processor can actually be a significant infrastructure burden -- Rails had to kind of completely change how it supported SCSS when SASS moved to supporting dart-sass as essentially it's only supported implemnetation (no more libsass or ruby-sass). And then what happens when one of your dependencies uses scss and another uses postcss. I understand the desire to get back to not using pre-processors.
You can definitely have your own goals and motivations and encourage people participating in this user poll to adopt them too. But if they aren't the goals the committee is working towards, they're unlikely to be achieved very well... and there are probably reasons they aren't.
So (IMO) we should absolutely be looking for what makes the most sense for human devs to write and read, and that should be the default for the base language that the browsers can interpret. We can always make it smaller via minification, etc.