Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-nesting] Syntax suggestion #4748

Closed
proimage opened this issue Feb 5, 2020 · 109 comments
Closed

[css-nesting] Syntax suggestion #4748

proimage opened this issue Feb 5, 2020 · 109 comments
Labels
css-nesting-1 Current Work

Comments

@proimage
Copy link

proimage commented Feb 5, 2020

This is kind of a follow-on from the closed issue #2701 and #2937.

Would it make any sense to implement native CSS nesting something like this?

body {
    background: black;
    color: white;
    ( /* note the open parentheses to indicate everything within is nested */
        main {
            background: orange;
            color: black;
        }
        p {
            font: serif;
        }
    ) /* close parentheses */
}

Or perhaps even like this (uses existing parser patterns):

body {
    background: black;
    color: white;
    @nest { /* or @nested, or @child, etc */
        main {
            background: orange;
            color: black;
        }
        p {
            font: serif;
        }
    } /* END NEST */
}

Basically, it would require any nesting to be placed within a separate grouping container, as a way to differentiate in bulk between attributes and nested selectors. Seems to me that this would have the benefit of not using the ampersand character and thus avoiding complications with pre-processors...

@fantasai fantasai added the css-nesting-1 Current Work label Mar 11, 2020
@vrubleg
Copy link

vrubleg commented Nov 12, 2020

The drawback is that it adds an additional level of indentation.

The mandatory & makes intention of a developer a bit more obvious and clear. I see no any good reasons of trying to avoid it. I would even make the @nest prefix mandatory for all nested rules to make it super explicit, but current spec is also fine.

@LeaVerou
Copy link
Member

So the proposal is that a bare @nest with no selector creates a rule that cannot contain any declarations but only CSS rules, which are all assumed to be nest-containing?

I wouldn't be opposed to that. It would save quite a few characters in nested rules with high breadth and low depth, and would make them easier to read too without the repetitive @nest.

Do note however that @tabatkins has objected to defaulting to descendant selectors when no & is present, and his arguments are quite sound.

@proimage
Copy link
Author

I'll be completely honest here—I don't fully understand everything going on here. ¯\_(ツ)_/¯ I build with CSS; I don't build CSS. ;)

So all I can say is that I have a strong gut feeling that implementing native CSS nesting in a way that breaks the compatibility that SCSS currently has with pure CSS is a bad move. Yes, I know the onus is not on CSS to retain that compatibility. Regardless, I feel like intentionally breaking it (or allowing it to break) when there are other ways to implement things will actively disrupt a large portion of the web.

I presume that for whatever reason, implementing native CSS nesting in the exact same way that SCSS already does it has been ruled out?

@LeaVerou
Copy link
Member

I presume that for whatever reason, implementing native CSS nesting in the exact same way that SCSS already does it has been ruled out?

The Nesting spec goes into detail about why that's not an option.

@vrubleg
Copy link

vrubleg commented Dec 17, 2020

@proimage Current spec is close to perfect, and it doesn't break compatibility with SCSS. SCSS couldn't be used in browser directly ever, you always had to use the SCSS preprocessor. So, just continue to use this preprocessor, and everything will be fine. It is not mandatory to use native CSS nesting when it will be available in browsers. But I wold use it, because it seems that it is designed a bit better than the SCSS nesting.

@proimage
Copy link
Author

Current spec is close to perfect, and it doesn't break compatibility with SCSS.

Great! If that's the case, then I have no objection. :)

@vrubleg
Copy link

vrubleg commented Dec 22, 2020

If authors of CSS preprocessors want to allow mixing of the native CSS nesting and SCSS-like nesting in one file, they could rely on @nest. If there is @nest before nested selector, it means that a user wanted to use native CSS nesting.

@mirisuzanne
Copy link
Contributor

My main issue with the specified approach is that there are two distinct syntax rules for authors, in order to solve a browser-parsing issue. While I understand the parser requirement in play, I don't like passing that along as an inconsistent syntax, where authors have to understand the parsing distinction. It would be great if we could move towards a more consistent single syntax, no matter what nested selector you plan to write.

In talking with @fantasai, we had a few ideas for a variation on the approach suggested above:

div {
  background: black;
  color: white;

  /* curly braces always fence off nested selectors, so we avoid syntax disparities */
  /* by default & is required in all selectors, to establish relationship */
  {
    & span { /* div span */ }
    p & { /* p div */ }
  }

  /* multiple nesting blocks are allowed */
  /* and are able to prepend a combinator for the entire block */
  /* (or & for descendant) */
  & {
    span { /* div span */ }
    p & { /* div p div */ }
  }

  ~ {
    span { /* div ~ span */ }
    p + & { /* div ~ p + div */ }
  }

  /* could also use & before combinator, for the same results */
  & ~ {
    span { /* div ~ span */ }
    p + & { /* div ~ p + div */ }
  }
}
  • It's a single, concise syntax for any type of nested selector
  • it allows the author to establish meaningful/readable shorthands that avoid repetition
  • the shorthand also helps to group selectors which share a similar relationship to the parent

@proimage
Copy link
Author

Hmm, interesting idea. Similar (and simpler, even) than the suggested @nest { /* selectors */ } option, just without even needing the @nest bit?

So what would multiple-level nesting look like?

nav.main-nav
{
   display: flex;
   {
      ul
      {
         list-style-type: none;
         {
            li
            {
               margin: 0;
               padding: 0;
            }
            a
            {
               display: flex;
               padding: 0.5em 1em;
            }
         }
      }
   }
}

Or, in the inline-braces style y'all seem to prefer for some reason... 😉

nav.main-nav {
   display: flex;
   {
      ul {
         list-style-type: none;
         {
            li {
               margin: 0;
               padding: 0;
            }
            a {
               display: flex;
               padding: 0.5em 1em;
            }
         }
      }
   }
}

All I'll say is, my eyes certainly aren't used to visually parsing that code... granted, color-coding would help a ton, but still... 😆

@davidwebca
Copy link

davidwebca commented Sep 22, 2021

I agree. I know lots of people have worked on this, but I've been following the discussion and I also feel like passing down the burden of having 2 syntaxes to solve a parsing problem to the CSS author is not the best move.

If @nest is able to forgo the requirement of the selector having to start with &, it just means it's a parsing issue that can be solved for the "regular" syntax too. And it's much cleaner to use and read when blocks are nested naturally with curly braces.

I'd say the last example by @proimage would be repetitive though, I would wish the curly braces to be unique per level if it was possible. Right now, CSS parses everything inside braces as a style declaration and that's the difficulty "@nest" and "starting with &" are trying to bypass : it's easier to parse the start of a nesting selector if it starts with something precise. But really if there's another curly braces block inside, it should be reverse parsed... or the rule could be "what precedes a curly brace needs to be a selector" point bar.

Again, same as some others chiming in: I have not worked on the parsers, I don't know the real difficulties of this, but if CSS parsers need to be rewritten to allow this, I would way prefer to wait and do it properly than ship this as it is.

Here's an example of the syntax I'm trying to explain:

.main-nav {
   display: flex;
   ul {
      list-style-type: none;
      li {
         margin: 0;
         padding: 0;
      }
      a {
         display: flex;
         padding: 0.5em 1em;
      }
   }

   nav& {
      display:  block;
   }
}

Becomes

.main-nav { display: flex; }
.main-nav ul { list-style-type: none; }
.main-nav ul li { margin: 0; padding: 0; }
.main-nav ul a { display: flex; padding: 0.5em 1em; }
nav.main-nav { display:block; }

@mirisuzanne
Copy link
Contributor

mirisuzanne commented Sep 22, 2021

@davidwebca Sadly, it's just not possible to "reverse parse" anything in CSS. For performance reasons, browser engines need to distinguish between a property and a nested selector with only a single token.

@proimage
Copy link
Author

proimage commented Sep 22, 2021

Here's an example of the syntax I'm trying to explain: * snip *

As was posted when I raised the very same issue, the answer is given here (in the expandable green details+summary box): https://drafts.csswg.org/css-nesting/#nesting

You're not wrong in that it would be an ideal syntax from a code authoring perspective, but from the perspective of the parser, it would exact too high a performance toll.

EDIT: Dangit, comment-sniped! ;)

@LeaVerou
Copy link
Member

I love this idea. I agree having two syntaxes depending on the location of the & is suboptimal.
This also means we can have the simple descendant syntax too, without having to prepend with & , making migration from preprocessor stylesheets as simple as wrapping rules with a set of {}.

@bradkemper
Copy link
Contributor

So, put simply, if, within rule braces, the parser encounters another opening brace, combinator, or ampersand (which isn't part of the value for a declaration), it should then go into selector parsing mode, instead of declaration parsing?

Interesting. I guess any preceding declaration values need to be capped off with a semicolon, to avoid a comma as a combinator being confused with being part of a value.

@proimage
Copy link
Author

proimage commented Sep 26, 2021

So, put simply, if, within rule braces, the parser encounters another opening brace, combinator, or ampersand (which isn't part of the value for a declaration), it should then go into selector parsing mode, instead of declaration parsing?

Interesting. I guess any preceding declaration values need to be capped off with a semicolon, to avoid a comma as a combinator being confused with being part of a value.

I think what we're talking about here is dropping the ampersand entirely (unless the parent selector needs to be injected in the nested selector in some other position) in favor of curly braces wrapping any nested selectors. Keeps the syntax simpler—no need for both & and @nest & support.

@proimage
Copy link
Author

proimage commented Sep 26, 2021

Would this open the door for selector concatenation via the &?

.card {
    display: flex;
    {
        &__image {
            display: block;
        }
    }
}

@davidwebca
Copy link

davidwebca commented Sep 27, 2021

Dropping & removes the ability to combine and reverse order of selectors so I wouldn't drop it. I think what we're suggesting here is to merely drop the requirement of the selector having to start with & (or to use @nest to allow it). Right now, the draft spec requires the selectors to start with & to pass the nesting requirement. Here's an example that is invalid:

.card {
    display: flex;
    .image& {
        display: block;
    }
}

To make it valid, you need to prefix it with "@nest" like so:

.card {
    display: flex;
    @nest .image& {
        display: block;
    }
}

So what we're saying, in short, is to drop the @nest requirement and stop dancing around the current parsers limitation to allow authors to have a cleaner syntax without those two versions that could end up being confusing and allow the first example to be valid (if it works with @nest, why wouldn't it be able to work without?)

Sidenote, I personally don't care about selector concatenation. That, I can understand it adds a level of complexity that jumps into many more parsing hoops and issues and I don't think people absolutely really need it. BEM authors can use BEM with combinations instead of concatenations (.card__image would be .card.__image) and still be readable. If it was possible without being too complex and without adding too much parsing time to browsers, I would like it, but maybe we're not there yet.

@tabatkins
Copy link
Member

I'm really not a fan of the additional set of braces; it adds two indents for each level of nesting.

I'm not sure what your additional three syntaxes are doing - is a bare selector allowed (not nested in an extra {}) if it starts with an &? If so, then I'm not sure what this gets us over putting @nest in front of everything - you'd still have two syntaxes and have to know when to switch from one to the other. You get to avoid writing a @nest keyword in front of each of your selectors, but in return you have to indent an extra level each time and deal with more matched braces.

Is the ~ one doing some implicit relative combinators? That's even more powerful than what Sass/etc currently allow, and means there's more context for a reader to carry into parsing the nested selectors - seeing a span {...} doesn't mean "a span that's a descendant of the parent rule's element" like it does in Sass, but instead can mean it has any of the four combinator relationships with the parent.

Actually, hm, it looks like you still can't put properties directly in the block; the nested block must contain style rules, not declaration lists, right? Then yeah, I'm still on the "two indents per nesting level sounds bad" train.

Finally, the current rules allow us to avoid having to write a heuristic for determining when a selector is meant to implicitly chain from the parent and when it's explicitly referencing the parent instead. These appear to bring that heuristic back, so we have to decide, for example, whether a & nested inside of an :is() or :not() counts as "referencing the parent" and so whether it means there's still a & implicitly prepended or not. This heuristic smells like an editing hazard to me, which is why I was so glad to be able to get rid of it with the current proposal, where all selectors are "complete" as written.

@davidwebca
Copy link

The double curly braces from @proimage were not necessary as detailed in my last comment. Is that what you're referring to?

I think the idea here is to mainly combine the regular syntax in the proposal with the @nest rule so that a nested selector is not obligated to start with & and avoid having 2 different nested selector syntaxes. 🤔

@proimage
Copy link
Author

I'm really not a fan of the additional set of braces; it adds two indents for each level of nesting.

I'm not the biggest fan of it either, but I think it's preferable over the current & … / @nest … & … proposal. Heck, perhaps in time our eyes would come to appreciate the extra level of indentation as an easy way to visually differentiate nested styles?

That said, what if we used some other separator character or series of characters to define a nesting block—one that doesn't have an implication of indentation?

I can't think of any single char that would fit the bill, but what about something like this?

div {
    color: red;
    ==== /* or ---- or &&&& or whatever */
    p {
        color: blue;
    }
    blockquote {
        zoom: 420;
    }
    ====
}

@mirisuzanne
Copy link
Contributor

I don't think it's hard to remove any heuristics from our proposal, and require explicit & wherever it's desired. That still leaves the double-indentation, but that doesn't bother me as much as a double syntax.

There would be another approach to achieving single-syntax, which is just to require @nest or some even-more-brief prefix on all nested selectors. Something like (using plain @ for an extremely terse example):

div {
  prop: value;

  @ & em { … }
  @ main & { … }
  @ & ~ div, p + & { … }
}

Though double-nesting still feels the easiest to read and write in my opinion.

@davidwebca
Copy link

davidwebca commented Sep 28, 2021

I agree. The double syntax bothers me more than anything else that we've been discussing. Whatever the decision in the end, I'd rather have a single syntax with more indentation OR @nest so that avoids all confusion when reading CSS code at a glance.

Then, I don't mind more indentation and I don't hate the "@" suggestion from @mirisuzanne above. My only gripe with @ is that it's already used to start special keywords like @media and @Keyframes. What about ">"? It must have been suggested before, has it? Or would it be too confusing with the direct child combinator?

div {
    color: blue;
    > table td & {
        color: orange;
    }
} 
div {
    color: blue;
    ? table td & {
        color: orange;
    }
} 
div {
    color: blue;
    - table td & {
        color: orange;
    }
} 

@tabatkins
Copy link
Member

ASCII soup isn't great if we can avoid it; anything we choose here becomes probably unusable in Selectors in the future, too.

If the group thought it was really worthwhile to have only a single form, using only @nest or even a bare @ is acceptable to me. I just fear it'll be too annoying for people already used to nesting in preprocessors where nothing is needed at all.

@proimage
Copy link
Author

If we're wanting to annoy preprocessor users as little as possible, then I think the "wrap nested selectors in a block" approach would be easier to transition to than the "append @nest/@ and/or & to each individual selector" approach.

If we go down that route, it would be great if the chars used to define said block were one of the ones already considered "containing chars" by code editors... i.e., the chars or char pairs that can easily be placed around a selection: ' ', " ", ( ), [ ], or { }.

To be honest, I'm still trying to figure out if a JSON-esque approach makes any sense:

div {
    color: red;
    nested: (
        img {
            display: block;
        }
    );
}

@davidwebca
Copy link

davidwebca commented Sep 29, 2021

Mmm that's an interesting idea that I didn't consider honestly. That's one thought I was bouncing around in my head when we were exchanging in the previous few comments: "@" rules usually have a set of specific rules (@keyframes, @media, etc.) and allow for specific functionalities, but nesting is just... allowing selectors inside a block , so why would we need an "@" rule when it's just about styles?

In that sense, the nested "style" declaration from @proimage makes syntactical sense. Plus, usually, at-rules only use parenthesis when there's a need to use special characters such as colons inside the arguments. Ex.: @supports (display:flex), @media screen and (min-width:280px)

So an interesting idea that would be easy on existing parsers would look like:

.card {
    display: block;
    (article&) {
        display: flex;
    }
}

would yield

.card { display: block; }
article.card { display: flex; }

But this conversation is running in circles because we're trying to solve three friction points at once when it might not be possible, unless we speak with people who code and optimize those parsers:

  1. Combine the two syntaxes (starting with & and @nest) into one to avoid confusion
  2. Prevent superfluous pressure on parsers by starting nested rules with a special character, ideally one that is not & to avoid confusion with its previous selector reference purpose.
  3. Try and reuse existing pre-processor syntaxes to facilitate migration

Also I want to clarify something about my previous comments on "reverse parsing". I didn't mean "let the parser do it's thing, parse and reverse engineer the selectors afterward". What I meant was to start parsing by the deepest level of nested blocks because it's easy to infer that the previous "rule" is a selector (what precedes "{" is always a selector or an "@" rule). It might be what they already do internally and I wouldn't know about it 🤷 but in that case, it would mean we don't need a starting character at all.

Note: Arguments made by @tabatkins here are still very much valid, but I'd love to hear them in a renewed way one year later.

@LeaVerou
Copy link
Member

LeaVerou commented Sep 29, 2021

Just brainstorming here: What if instead of the curly braces, we prepend the list of nested rules with something? E.g. @nested;. That would solve the problem of the extra nesting level.

Alternate idea: What if only the first rule needs to start with &, then we can just assume the rest are selectors and not properties. Then, if anyone wants to handle this generically (e.g. when migrating from a preprocessor), only 3 characters need to be prepended to the list of nested rules: &{}.

@fantasai
Copy link
Collaborator

fantasai commented Oct 1, 2021

You get to avoid writing a @nest keyword in front of each of your selectors, but in return you have to indent an extra level each time and deal with more matched braces.

@tabatkins That seems like a really good trade-off for not writing @nest in front of every selector, which is both a) annoying to type and b) uselessly noisy.

I don't understand the resistance to another level of indentation. If your indents are too long, stop using 4 spaces. If you're not mixing in declarations you can also just double up your braces and indent one level.

outer {{
  inner { something: foo; }
  more { other: foo; }
}}

@LeaVerou I don't think I like that kind of statefulness, where what's parsed before as a sibling construct affects so fundamentally what's parsed after.

@argyleink
Copy link
Contributor

we should be getting more feedback from a broader audience

✅ Poll has run long enough to share the results:

image

6,661 votes for @nest, 338 votes for @nest always, 541 votes for brackets



Should we add this to the next agenda for discussion?

@davidwebca
Copy link

Where was this poll even posted. Rip, completely missed it... I'm not sure it reached the audience 😬

@vrubleg
Copy link

vrubleg commented Aug 10, 2022

It was posted here: https://developer.chrome.com/blog/help-css-nesting/

@chee
Copy link

chee commented Aug 19, 2022

i prefer @nest always because it means i don't have to learn when to use @nest

@bradkemper
Copy link
Contributor

I prefer the combination of @nest and ampersand. To me it feels like the easiest transition from using SCSS. I don’t mind using & .child (with the space in between), because I am already used to doing things like & .child1, & .child2 in SCSS. I honestly don’t know if that is necessary or not in SCSS when using selector lists, but it always feels safer and more clear. Using @nest Only would be be too cluttered IMO. I’ve tried to warm to the idea of using bare curly braces, but I think it would be harder to get used to, and more prone to error: I think it would be harder to spot the places where I left them out accidentally.

@scherii
Copy link

scherii commented Aug 19, 2022

@chee If I understood everything correctly, all you need to know about @nest (first variant) is that the notation known from SCSS comes into play, except when the nested element is a parent of it (e.g. styling an element based on if a dialog is open: html:has(dialog[open])).

So there is not much to learn.

Whereas @nest always feels extremely verbose to me, and depending on the codebase, could blow everything up quite a bit. brackets, on the other hand, increases indentation by one compared to the other two.

TL;DR:

  1. @nest = minmal, concise, with the possibility of using @nest for parent selectors
  2. @nest always/restricted = verbose, tends to bloat the codebase
  3. brackets = increases indentation by 1

@LeaVerou
Copy link
Member

Since this is on the agenda for today and I can't join, I'll post my thoughts here:

While the current spec is not ideal (authors basically want Sass-style nesting), it's far, far better than any of the other solutions posted here. @nest all the time would make stylesheets tedious to write and noisy to read, and the brackets would make them highly nested, and would need horizontal scrolling to read.

I have been using the current syntax very extensively through PostCSS and it works well. I very rarely need to use @nest at all, in nearly all cases it's just &. I'd propose we stop trying to fix something that isn't broken, and just close this issue. We've explored the design space sufficiently, and there doesn't seem to be nothing better that satisfies the parsing constraints.

@proimage
Copy link
Author

I concur, but I think it would be nice to have this close with a summary of the agenda discussion, so I won't close it quite yet.

Thank you, all, for a very thorough, 2.5 year (😲) look at this issue!

@mirisuzanne
Copy link
Contributor

I agree, in practice the spec syntax works best, despite the apparent complexities. But at this point we actually need to revert the previous resolution - where we voted to use brackets instead. As one of the people behind that resolution, I'm now fully in support of reverting it, and implementing the spec as originally written.

@LeaVerou
Copy link
Member

I agree, in practice the spec syntax works best, despite the apparent complexities. But at this point we actually need to revert the previous resolution - where we voted to use brackets instead. As one of the people behind that resolution, I'm now fully in support of reverting it, and implementing the spec as originally written.

Yikes, yes. How did that happen?

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed Nesting syntax, and agreed to the following:

  • RESOLVED: Revert the previous resolution from Nov 2021 mandating bracket-nesting syntax, and the WG preference for a single nesting syntax.
The full IRC log of that discussion <TabAtkins> Topic: Nesting syntax
<TabAtkins> github: https://github.com//issues/4748
<fantasai> ScribeNick: fantasai
<fantasai> TabAtkins: Some time ago we had a discussion about what style of nesting syntax should use
<fantasai> TabAtkins: major options are what I drafted, nesting selector directly with an &
<fantasai> TabAtkins: or use @nest if needed
<fantasai> TabAtkins: option 2 is @nest always
<fantasai> TabAtkins: and option 3 is using brackets to wrap nested rules, never use @nest
<fantasai> TabAtkins: At the time we did a straw poll, and WG resolved on using brackets
<fantasai> TabAtkins: I was unhappy with this and we resolved to take it back for more data collection or arguments
<fantasai> TabAtkins: Adam Argyle ran a poll with significant response numbers about this
<TabAtkins> https://developer.chrome.com/blog/help-css-nesting/
<TabAtkins> https://github.com//issues/4748#issuecomment-1211280306
<fantasai> TabAtkins: Linked article, I think was written fairly
<fantasai> TabAtkins: It seems responses are incredibly one-sided
<fantasai> TabAtkins: Using & or @nest directly
<fantasai> TabAtkins: This is what's in the current spec and what I prefer
<fantasai> TabAtkins: This was an overwhelming response in favor of one option
<fantasai> TabAtkins: So suggestion is to revert previous resolution and keep syntax in current spec
<astearns> option 1 also allows you to write as if @nest is always required
<Rossen_> ack fantasai
<miriam> q+
<TabAtkins> fantasai: The problem I noticed reading thru the thread is the bracketed syntax was represented as always putting in the ampersand, while the point of bracketed syntax was to avoid needing it
<TabAtkins> fantasai: So I dont' think that's true that it was fairly written
<TabAtkins> TabAtkins: I didn't take that as part of the syntax at all, still needed the & to be the nesting selector
<TabAtkins> fantasai: The point was that it was mostly never needed, so I think the poll wasn't valid
<TabAtkins> miriam: I reviewed the article and helped come up with the syntax, so...
<Rossen_> ack miriam
<TabAtkins> miriam: I didn't think of removing the & as the main advantage of the brackets
<TabAtkins> miriam: As I was writing it I foudn the brackets more confusing than expected, and I actually *added* ampersands for clarity
<TabAtkins> miriam: AFter writing the demos I actually changed my mind away from bracketing
<TabAtkins> fantasai: I'm not going to block, if y'all liek this syntax better that's fine. I just don't like having an inconsistent syntax.
<TabAtkins> fantasai: I just don't think it's fair to say it was overwhelmingingly in one direction
<TabAtkins> Rossen_: So it sounds like we have a pretty strong response. Possibly biased, but Mia makes an argument for it not being so.
<fantasai> I really don't like the inconsistency, where you have to know when to use @nest
<Rossen_> q?
<TabAtkins> Rossen_: So proposed resolution is to undo the resolutions from november and leave the spec as it currentyl is
<TabAtkins> Rossen_: Any additional comments or feedback?
<TabAtkins> fantasai: I'm not gonna object because everyone seems to like it, but I really don't like that authors have to know when to use @nest, it's not a consistent syntax
<TabAtkins> fantasai: And I wish we had something else besides an inconsistent syntax
<TabAtkins> fantasai: I don't like @nest everywhere since it's verbose, but...
<TabAtkins> miriam: I agree on the problem and it's why I liked bracket before. I agree it's an issue and it's weird
<TabAtkins> miriam: But once I started writing examples, it almost all basic use-cases you just use the & and it works and looks cleaner.
<TabAtkins> miriam: and there are only rare cases where you need to use @nest and it's not as hard as I initially thought to know the difference
<TabAtkins> miriam: So that's why I changed my mind even tho I agree it's inconsistent
<bradk> +1 to miriam
<TabAtkins> plinss: I think elika has a valid point and before we decide on this we could make another design phase to try to come up with another route.
<fantasai> I'm not so opposed to the & , just that some rules require @nest and others dont'
<TabAtkins> plinss: I'm okay with undoing the previous resolution, just not sure we shoudl resolve affirmatively on the current syntax
<TabAtkins> Rossen_: Right, so let's just resolve on undoing the previous resolution.
<TabAtkins> Rossen_: Objections?
<TabAtkins> RESOLVED: Revert the previous resolution from Nov 2021 mandating bracket-nesting syntax, and the WG preference for a single nesting syntax.
<TabAtkins> Rossen_: And for the record, want to strongy encourage continued improvement of the design.

@romainmenke
Copy link
Member

romainmenke commented Aug 31, 2022

Didn't see it come up in the minutes but I think the results of any poll directed at stylesheet authors will be biased when it comes to nesting.

In this case they were given the choice between the syntax that they use today with postcss-nesting or two alternatives.

They will always pick the one that they use today.

If they were given the choice for sass-like nesting they would have picked that because it is still the mental model for nesting for the majority of authors.

This is the downside of pushing out polyfills for features that aren't stable even in spec form. In postcss-preset-env we are moving much more slowly now but we can't take back nesting :/


meaningless extra data points :

It seems that there were some question about & vs @nest initially but mostly people seemed to find their way even without a single syntax for nesting.

@vrugtehagel
Copy link

vrugtehagel commented Sep 1, 2022

I'm surprised the "parser switch" option suggested by LeaVerou got so lost in the mix. I actually liked that option a lot. It doesn't have the inconsistencies of knowing where to do what, it doesn't have the double indention and it doesn't require continuous repetition of the @nest keyword. I'll highlight the way it works here once again. Essentially, after your regular style declarations, you can write @nest; to start your nested rules. For example:

button {
    font-size: inherit;
    color: white;
    background: blue;
    border: none;

    @nest;
    &.btn-special {
        color: yellow;
        background: gold;
        
        @nest;
        &.btn-large { font: 3rem / 1.5 fantasy, sans-serif; }
    }
    menu & {
        text-decoration: underline;
    }
}

This is

  • easy to read. It has the same visual structure as SCSS. In fact, it might be even easier; the syntax enforces the style rules to be before any nesting, making sheets predictable, and given most developers have syntax highlighting, the @nest; just makes it a tiny bit easier to recognize where the declarations are (above the @nest) and where the nesting happens (below the @nest).
  • easy to edit. Adding or removing a declaration? No problem, you can do that just like you used to. Adding a nested rule? Write @nest; if it doesn't have it already, and write your nested styles as you wish. No need to worry about where the & goes in your selectors, and no need to prefix every single nested selector. Removing a nested rule? Simply... remove it. If there's a dangling @nest;, you could even leave it and it'd be fine; or you can remove it, keeping your sheets a bit cleaner.
  • easy to learn. The rule is simple. You nest, you need a @nest; after your declarations. The nested selectors are then just SCSS-style selectors with &.

I struggle to see the issues with this. The CSSOM would be relatively simple, leaving out the @nest; rule completely (as its presence would already be indicated by the fact there are child selectors).

Anyway, I just wanted to pull this option out of the dark in case it was overshadowed by the other three options. Would love to hear what people's opinions are on this one.

@proimage
Copy link
Author

proimage commented Sep 1, 2022

Anyway, I just wanted to pull this option out of the dark in case it was overshadowed by the other three options. Would love to hear what people's opinions are on this one.

I assume that a simple child selector would not need the ampersand? If so, I love it.

To convert existing SCSS to this (assuming the SCSS author isn't an absolute monster who mixes styles with nested selectors), one would simply add @nest; above the nested selectors, separating them from the styles themselves. If you think about it, it's kind of like a <hr> of sorts. Heck, I imagine some of the more visually-oriented authors might like to utilize CSS comments to make this more visible, eg:

/* ------------------ */
@nest;
/* ------------------ */

/* or perhaps just */

@nest; /* ------------------ */

Perhaps for the sake of those cases where the CSS cannot be fully controlled by authors (restrictive CMSes or whatever), an optional @endNest; (@tsen;? 😆) terminator might be useful? 🤷‍♂️


The only problem I can see with this syntax is the inconsistency of how CSS uses @______ code. Most often, it's:

@something (parameters) {
    /* stuff */
}

...which, bringing this right back around to the very starting post, is why I suggested the @nest { /* selectors */ } syntax in my very first post. 😉

Granted, even the current spec of @nest & .selector {...} breaks that mold, so if that's ok, then I have no problem with LeaVerou's syntax. 👍

@vrugtehagel
Copy link

Indeed, an ampersand wouldn't be required. I would write it anyway, and we can require it if we want to, but the @nest; is enough for the parser to know it's getting selectors from there on out, so there's no ambiguity at play.

Not sure if an @end-nest would be necessarily useful; one can always just re-write the entire selector (and concatenate it to the stylesheet). Perhaps it could be something for the future if the need is there; but for now let's keep it basic.

The inconsistency about at-rules is valid, but only to a certain extent; CSS already has things like @charset "utf-8"; and @import url(https://example.com/sheet.css);, which are not followed by a block at all. There's the @page rule which doesn't need (parameters) and CSS recently got @layer which kind of just does everything (i.e. @layer foo;, @layer foo {} and @layer {} are all valid). Granted, @nest; is a little different from all of them because it's not followed by a block nor an ident but given what CSS already has I really don't see why @nest; would syntactically be an issue.

@tabatkins
Copy link
Member

Having a parser switch like that would be unprecedented in CSS; the closest thing we have to it is the @charset syntax, which also looks like an at-rule but isn't and has effects on how we parse the rest of the document.

Other things like @import or @layer statements aren't similar; they're just normal at-rules that happen to not need a block. The parser doesn't need to know a thing about them.

@vrubleg
Copy link

vrubleg commented Sep 1, 2022

LeaVerou suggested even more interesting idea which would allow to use any nested rule starting from & as the switch (or just &{} if the first nested rule doesn't start from &). But we probably don't want to discuss it for another few years =)

@vrugtehagel
Copy link

Having a parser switch like that would be unprecedented in CSS

Yes, but nesting itself would be unprecedented too. Perhaps the tradeoff is worth it?

As for the comparisons to @import and @layer; I was referring to the visual syntax only. A point was raised that @something (parameters){ /* code */ } seemed to be the mold at-rules conform to, and I was merely providing examples that do not have (parameters) or { /* code */ } blocks. I understand the differences in how they are treated.

Anyway, in an attempt to push this issue ever so slightly closer to resolved, I'd like to logically go through the options we have, with the assumption that we do not want to introduce new concepts such as parser switches.

Of course, for this syntax to be usable, it necessarily needs to be somewhat resembling SCSS nesting. People have been using that for a very long time, so it is familiar to them, and it's what they want in CSS. Now, let's say we have the following SCSS:

button {
    color: red;
    aside & { color: blue; }
}

In CSS, we will need to somehow distinguish the aside & from the color; we don't want to do nasty lookaheads and so inevitably the @nest at-rule is born. There are now two sensible, familiar options for the @nest rule: We have @nest (selector) { /* declarations */ }, or @nest { /* rules */ }. The former is what is currently in the spec, but with a quirk that you don't need to use @nest for rules starting with an &. The latter is essentially the double brackets syntax, although we dropped the @nest (which seems to me would be more outrageous than adding a parser switch @nest, but that opinion is coming from a developer, not an implementer). Those are the options. We cannot avoid needing @nest for every rule not starting with &, unless we use double brackets; and neither option is great.

My point here is, we can try to think of different syntaxes all day long, but the viable candidates are extremely limited when it comes to syntax structures we already have, especially given constraints like "no lookaheads" and "no inconsistencies". The parser switch would be a massive change for parsers, yes. If implementers are against it, I would understand. But I haven't seen an implementer actually object to the idea.

In my opinion, the @nest; parser switch is, so far, the best candidate we have when it comes to usability of the language. When I think about it, the language is already called "Cascading Style Sheets"; having this parser switch actually fits quite well.

@mirisuzanne
Copy link
Contributor

The more I look at the current syntax, the more convinced I am that there's no 'inconsistency' to worry about, and the 'double syntax' pretty cleanly reflects two different tools that are both useful in nested CSS. And these two types of nesting are actually pretty clear to authors:

  • Most nesting involves adding to the end of the selector chain. That's the highest priority use-case, and it works as expected, using selectors that start with &.
  • Some more advanced nesting use-cases involve adding to the start of the selector chain, or around it. In those cases, the @nest rule is used.

Sass has a very similar special-nesting feature in the @at-root rule. It does basically the exact same thing as the proposed @nest. The main difference is that Sass doesn't require the at-rule as consistently when doing advanced nesting.

While it's true that CSS has to do this for obscure internal parsing reasons, the resulting logic is not at all obscure, or arbitrary, or difficult to learn & teach. It's actually more clear than the Sass logic, since it aligns more directly with a clear semantic difference that authors already think of as two different 'types' of nesting: appending vs manipulating.

@nightpool
Copy link

My confusion/concern with the nesting syntax was always the lack of clarity caused by having "anonymous" blocks that just added extra braces for no clear-to-the-user purpose. I think that if the poll had included an option that required @nest before every set of brackets, opinions would have been less decisive. Along with what @fantasai already said about & being implicit, what would people think about a proposal that supported this example:

figure {
  margin: 0;

  @nest {
    > figcaption {
      background: lightgray;

      @nest {
        > p {
          font-size: .9rem;
        }
      }
    }
  }
}

it still has the double-indent issue, but I think it resolves a lot of the clarity/ambiguity issues that really turned me against the brackets proposal initially.

@chrishtr
Copy link
Contributor

Closing as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-nesting-1 Current Work
Projects
None yet
Development

No branches or pull requests