top | item 34006622

Help choose the syntax for CSS Nesting

103 points| feross | 3 years ago |webkit.org | reply

159 comments

order
[+] jasonhansel|3 years ago|reply
I prefer Option 3, but IMHO the "&" should be required in all cases. Having it present in some places and absent in others makes the resulting code less readable, more ambiguous, and generally harder to understand. I'd rather keep things simple and uniform, even if it costs extra keystrokes.

Also: it would allow things like ".a { &.b { x: y; } }" (which would be equivalent to ".a.b { x: y; }") to be clearly differentiated from ".a { & .b { x: y; } }" (which would be equivalent to ".a .b { x: y; }"). Just ".a { .b { x: y; } }" looks ambiguous between the two, since it doesn't "look like" a descendant selector.

[+] jjcm|3 years ago|reply
I don't think it needs to be required, but I'd 100% include that in any linter or style guide I had for code.

For production code, I'd use it 100% of the time. For just quickly reorganizing some syntax around and copy/pasting things, I appreciate the shorthand version. I wouldn't use it in a codepen in most situations.

[+] chrismorgan|3 years ago|reply
I very strongly agree. Mandatory & makes things much clearer, for a negligible cost. (And really, the curly braces, colon and semicolon are all mandatory (well, except the last semicolon of a ruleset), and alternative syntaxes with less punctuation like Sass’s have failed; the & is frankly more consistent than its absence.)

For a more technical justification: with optional &, priority is given (by brevity) to the descendant combinator, but what of other combinators? Do they require & or not? It could reasonably go either way. I haven’t read recent proposals, but am very familiar with CSS grammar, and I’m genuinely unsure.

  parent {
      /* This might or might not work. */
      > child {
          …
      }
  }

  parent {
      & > child {
          …
      }
  }
[+] franciscop|3 years ago|reply
Optional and being able to require it with e.g. a linter on a per-project basis seems better idea for me, since I'd strongly prefer not using & and for the vast majority of cases (as we learned from SASS) it's not needed.
[+] mattwad|3 years ago|reply
I don't know, I have been using SASS/LESS for a while and I almost never use the & because 95% of the time, I am talking about a descendant when I use nesting. I think it would be very easy to accidentally typo or miss in a review the difference between "& .sub-class" and "&.sub-class."
[+] Vinnl|3 years ago|reply
Unfortunately, you would still have the problem of seemingly-arbitrarily needing `:is` in some cases, when the & isn't at the start of a declaration.

So e.g.

  .someClass {
    :is(a) > & {
      font-weight: 700;
    }
  }
instead of the more intuitive (but impossible)

  .someClass {
    a > & {
      font-weight: 700;
    }
  }
[+] eyelidlessness|3 years ago|reply
I agree completely, and having followed this since it was a pipe dream, I’m actually sad that the machine parsing and unambiguous human readability convergence lasted so long to hold this up only to fall apart as it becomes a real possibility.
[+] culi|3 years ago|reply
strongly agree as well. Option 3 with required "&"
[+] asd11|3 years ago|reply
Get WhatsApp live
[+] joeydi|3 years ago|reply
I think the reason this debate continues to rage on is that all of the options are worse than what is currently available with pre-processors.

If CSS can’t match or improve on the obvious syntax used by sass then they should not implement it.

All of these options are confusing. Nesting is not a requirement for CSS. Just let it go and if you want nesting use sass.

[+] aliyfarah|3 years ago|reply
> Nesting is not a requirement for CSS.

Not a requirement because it’s not in the spec or because developers don’t need it? If the former, the proposal is to make it a requirement. After CSS grid, CSS nesting is probably the most requested feature of CSS for as long as I can remember for web devs. I only use sass these days for nesting and it would be nice if I didn’t need that extra dependency.

[+] toastal|3 years ago|reply
I've stopped using preprocessor for years now. Almost everything I need is now in the spec or solved ‘good enough’ through a naming scheme. IMO, a preprocessor is a lot of complexity for little gain currently.
[+] Vinnl|3 years ago|reply
I do wonder whether "no nesting" should've been an option in the poll. Not because I necessarily think it's the right option, but it might be: if this gets added to CSS, but people keep using other tools for easier nesting, then we're stuck with that implementation nonetheless.
[+] barnabee|3 years ago|reply
I try not to use preprocessors or bundlers/build tools for simple stuff so the addition of features like this to the base CSS language is something I look forward to.
[+] RheingoldRiver|3 years ago|reply
Preprocessors aren't always available though. For example, I almost never have access to one, developing with MediaWiki (there is an extension that makes it available but most of the wiki farms I've worked for have not had it installed).
[+] eyelidlessness|3 years ago|reply
> If CSS can’t match or improve on the obvious syntax used by sass then they should not implement it.

And this is the problem. For so long there was a technical limitation because parsing complexity, then there suddenly wasn’t a limitation anymore and the existing syntax wasn’t good enough anymore so we’re shooting the moon.

I agree, all of the options are confusing. The only seemingly viable option is close to Sass, but it should just be strict and stop introducing more ambiguity to the grammar which is already absurdly complex.

[+] mekster|3 years ago|reply
For sake, Stylus doesn't even require colons and semicolons with plenty more features that was invented more than a decade ago and CSS is still trying to sort out things that was solved in 2010 in worse ways in 2022?

This is beyond comprehensible. I guess I can never let go of preprocessors.

[+] slinger|3 years ago|reply
> Everyone wishes CSS nesting could use the same kind of simple syntax that Sass does. That’s impossible, however, because of the way browser parsing engines work.

I wish browser vendors could work around this limitation and stick to Sass syntax. IMHO all of those options seems strange and make it difficult to read.

[+] Sesse__|3 years ago|reply
> I wish browser vendors could work around this limitation and stick to Sass syntax.

FWIW, it is not a limitation, it's a conscious choice. If you want to accept arbitrary selectors in option 3, you'll need unlimited lookahead, which limits the types of parser algorithms you can use, which means that _all CSS_ (not just CSS using nesting, every single web page out there) will be slower to parse, which means the web will become ever so slightly slower. And you cannot ever go back after making that choice. Is it really worth it for the few unusual cases that require workarounds, in an already not-super-common feature?

(Disclaimer: I wrote Chromium's CSS nesting support. It currently implements option 3.)

[+] wnevets|3 years ago|reply
> stick to Sass syntax. IMHO all of those options seems strange and make it difficult to read.

That is my biggest take away from this whole thing. The SASS/LESS syntax is so much easier to read to me.

[+] jorams|3 years ago|reply
I'm a bit confused about the constant mention of option 3 in this article, because it seems to match none of the actual options from the original poll. The original poll offered three options:

Option 1: @nest. Requires either starting a nested selector with &, or prefixing it with @nest if the & should not be at the start.

Option 2: @nest restricted. Always requires starting with @nest.

Option 3: Additional brackets to group nested selectors.

The "option 3" mentioned in this article matches none of those. It's like option 3 without the defining characteristic of the brackets, or option 1 without the required nesting indicator (but now with the restriction that the selector needs to start with a symbol).

None of these options are particularly great. The new "option 3" looks cleanest, but the symbol requirement is going to trip people up.

[+] culi|3 years ago|reply
> Additional brackets to group nested selectors.

Isn't this what Sass does? Option 3 seems to me to be exactly Sass nesting except the "&" is required in more places than it is in Sass. I personally love the "&" and already write my Sass that way anytime I nest

[+] CSSer|3 years ago|reply
Wow. I’m really surprised so many people have voted for option 3 so far. Option 5 feels much more in line with the rest of the other proposals and features in CSS, and option 3 introduces at least one quirk we’ll have to internalize and teach to everyone forever. Those suck. I have to teach those kinds of things to juniors until my throat is sore some days.

I don’t have much of substance to say about option 4 other than that it just feels… gross. I’m sorry, but those butterfly brackets are really extra.

Circling back, I think my preference for option 5 is that I personally moved away from nesting with preprocessors after watching too many developers create specificity bombs in their CSS from selectors nested 4 or 5 layers deep. I think it makes sense for this syntax to have a clear, distinct weight to it. CSS should be meant to be read just like all other code.

Finally, and I won’t spend too much time on this because I’m not trying to be a detractor, I’m not sure this is really high up on my wishlist anymore. Component-based design has changed a lot about the way I write CSS. I’ve found that I write a lot fewer highly-specific selectors these days, and I never have to bother with heavy id/class syntax conventions like BEM anymore for things to be easy to understand either. Those two things alone were big, dangerous motivators for me to use or encounter nesting. Anyway, I voted for option 5.

[+] alwillis|3 years ago|reply
Thanks for the thoughtful response.

No. 3 is the knee jerk, in the heat of the moment response; but no. 5 makes the most sense long term.

[+] dmtroyer|3 years ago|reply
genuinely curious, how does option 5 prevent specificity bombs?
[+] LordDragonfang|3 years ago|reply
Option 3 seems the most intuitive, but I can see the arguments for 5. It's 4 that I'm having a hard time understanding why anyone would want it that way. It's truly awful. I can't think of literally any other language (style, markup, programming, or otherwise) that does nesting with a second set of adjacent (not nested!) curly braces.
[+] halostatue|3 years ago|reply
Same. I think that the only thing I don’t like about 3 is the need for `:is(aside)` and the like, and I can see that there might be some performance benefit to `@nest {…}` blocks (5). I think the only thing I don’t like about 5 is the required `& {}` block.

I find 5 to be the most consistent and 3 to be mostly cleaner because of what becomes optional.

[+] TrianguloY|3 years ago|reply
I voted for 4. For me it's the best option with }{ on a separate line.

    html {
    }{
      & body {
        width: 100%;
      }
    }
You have a clear distinction between the "standar" rules for the current object and then a different set of rules for nested ones. While reading css, on the other options you can mix both styles which (specially with the optional & ) can become easy to misunderstand.
[+] martin_a|3 years ago|reply
Do we really need this?

I find all the examples harder to read and understand than combining classes/elements.

[+] inopinatus|3 years ago|reply
One could be forgiven for thinking any committee depraved enough to enumerate the options sequentially as 5, 4, or 3, moreover where option 3 is not even the same as the prior option 3, has implicitly renounced any standing to be offering solutions in re. developer ergonomics.

Come back, DSSSL, all is forgiven.

[+] swlkr|3 years ago|reply
I actually like Option 3 the best even more than sass syntax.

It's very explicit whereas sass is implicit and harder to understand at a glance when nesting happens, especially when you throw in an & and have styles above and below the nested parts.

[+] runarberg|3 years ago|reply
It feels weird that the CSS WG does a popular vote for a different syntax implementing the same feature, something that an expert opinion should weigh way more then popular demand. But at the same time TC39 picked an archaic version of the pipeline operator (|> https://github.com/tc39/proposal-pipeline-operator) despite a clear demand from the community to pick a functional one.

Perhaps the two committees should learn a bit from each other.

[+] jacobp100|3 years ago|reply
The version they’re picking is the fast pipe from OCaml, and the only pipe in ReScript. It works much better for type inference, and avoids the extra allocations the FP pipe usually incurs
[+] sfink|3 years ago|reply
I claim that an improved option 4 would be:

    parent {
       ...parent rules...
    } @nest {
       ...nested rules...
    }
by analogy with `try { ... } catch { ... }` syntax. Looks a lot better than the line noise `} {` thing.

An improved option 3 would be to unconditionally require the &.

Another alternative would be to stick with Sass syntax but require nested selectors to use :where:

    parent {
        ...parent rules...
        :where(child:pseudo) {
            ...nested rules...
        }
    }
and maybe allow :nest(...) as an alias of :where(...), except disallow it at the top level.

    parent {
        ...parent rules...
        :nest(child:pseudo) {
            ...nested rules...
        }
    }
Or play a similar weird trick to the original option 3 and only require :where(...)/:nest(...) for selectors that begin with a letter. But that starts to look a lot like option 3. Especially if you always require &, because then & can be used to disambiguate the child:pseudo parsing problem.

I'm not familiar with the past discussion of any of this, though.

[+] sublinear|3 years ago|reply
All these options are pretty terrible yeah. I'd prefer if there was work done towards selectors for parent items, siblings of parents, and children of parent siblings. Not that the work is mutually exclusive.
[+] frosted-flakes|3 years ago|reply
One big advantage of option 3 is that it allows for putting properties and nested selectors in any order, rather than segregating them in different groups. This is already possible in Sass and I do it all the time. For example:

    .card {
        border: 1px solid;
        &:first-child {
            border-top: none;
        }
        background: blue;
        &:hover {
            background: red;
            color: yellowgreen;
        }

        display: flex;
        // etc.
    }
[+] LudwigNagasena|3 years ago|reply
> Everyone wishes CSS nesting could use the same kind of simple syntax that Sass does. That’s impossible, however, because of the way browser parsing engines work.

I may be in a minority, but I think that what-you-write-is-what-your-browser-parses should be ditched as a norm. There should simply be an efficient (binary) format that is treated as an irrelevant implementation detail.

What is even the problem? People use preprocessors, poly-fills, hot-reloading, etc anyway. It isn't 1990s.

[+] alwillis|3 years ago|reply
> What is even the problem? People use preprocessors, poly-fills, hot-reloading, etc anyway. It isn't 1990s.

All of this tooling is used because of missing CSS features for the most part.

The web browser's ethos is to be a viewing and authoring environment; from Netscape Navigator on, browsers have enabled its users to write HTML and later, CSS.

For that to continue, browsers we can't have web authoring that's dependent on preprocessors, etc.

Another way to think about it: in an alternate universe where CSS had all of its current features like custom properties (a.k.a. CSS variables), nesting and most of the other modern features, there would be no Sass because we wouldn't have needed it.

In our current reality, CSS is maturing to the point where the work-arounds we implemented aren't going to needed for much longer. We can do that without breaking how the web has always worked.

[+] the_other|3 years ago|reply
Controversial opinions follow:

Nesting promotes bad CSS which exacerbates the problems people encounter elsewhere in CSS (like excessive specificity).

Nesting discourages paying attention to the global scope of the language and we already have many solutions to that problem for those of us who like to pretend the global scope isn't there.

Nesting grossly accentuates up front write-time benefits, whilst degrading the later read-time experience, actually making long term support harder.

[+] claytongulick|3 years ago|reply
I like option 3 the best, but what I'd really prefer would be block-scoped css blocks that could optionally be scoped just to children of the parent tag.

I know this is why we have shadow dom, but in practice I feel like the shadow dom has caused more problems than it solved.

Now we have a bunch of ::part things being added to make up for the problems, but it's still very difficult to work with.

I'd love to have the ability to put a <style scoped> tag inside a div and have it's rules only apply to the contents of that div, optionally overriding parent styles.

I don't know about the challenges implementing that in the engines, but it seems like it would be straightforward since the functionality already exists, style tags are valid markup almost everywhere in the dom, they're just parsed out and combined. For this you'd just have to track the parent element and bind to it like an ID selector.

This would make web component authoring more pleasant.

Edit: it looks like the functionality I'm asking for has already been considered, partially implemented, and is now deprecated, sadly.

[+] Sesse__|3 years ago|reply
Have you looked into @scope?
[+] DitheringIdiot|3 years ago|reply
Without a "none of the above" option, it's hard to tell what people really want.

Quite a few comments here aren't happy with any of them.

[+] specialist|3 years ago|reply
Whichever option the boffins decide, nested and flattened should be isomorphic. And used interchangeably.

Like this, but for CSS: https://github.com/wnameless/json-flattener

Sharp observers will notice that flattened declarations look like path expressions.

While CSS selectors are nice, path expressions enable concise drill down to precise modifications (overrides).

For future, every programming language should have intrinsic path expressions. Basically a useful, usable, legible version of LINQ.

--

PS- I really don't get the '&' ampersand. It's like a reference?

"foo.bar { ... }" is sugar for the expanded "foo { bar { ... }}". Makes sense. (No ampersand required.)

But "foo { bar & { ... }}" being the same as "bar foo { ... }" breaks my brain. What's the use case for reversing the parent-child relationship, contrary to the lexical order?

Update: Oh. It's a SASS thing. Meh.

[+] TrianguloY|3 years ago|reply
But option 3 from the previous poll is not the same as option 3 now...I don't understand