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-1] Yet another nesting proposal (Option 5) #7970

Closed
plinss opened this issue Oct 27, 2022 · 33 comments
Closed

[css-nesting-1] Yet another nesting proposal (Option 5) #7970

plinss opened this issue Oct 27, 2022 · 33 comments

Comments

@plinss
Copy link
Member

plinss commented Oct 27, 2022

The fundamental problem with allowing nested style rules is disambiguating properties from nested rules.

So far, every proposal has focused on taking a regular style rule and creating a syntax to identify selectors within it. What if we turned that around?

My proposal is to have an at-rule that has a selector, and contains nothing but style rules (and other at-rules), whose selectors are relative to the at-rule. We can then have a special block within the at-rule to hold properties that apply to the nesting root,

E.g.

@nest foo {
    & { color: blue; }
    bar { color: red; }
}

would be equivalent to:

foo { color: blue; }
foo bar { color: red; }

You could also, of course nest the at-rules:

@nest foo {
    & { color: blue; }
    @nest bar {
        & { color: red; }
        baz { color: green; }
    }
}

would be equivalent to:

foo { color: blue; }
foo bar { color: red; }
foo bar baz { color: green; }

It may also be possible to omit the & and just use a bare {} block within the at-rule to hold properties.

This requires no changes to existing parsing, OM, or behavioral rules and has a straightforward OM for the new at-rule.

@fantasai fantasai added the css-nesting-1 Current Work label Oct 27, 2022
@Loirooriol
Copy link
Contributor

Loirooriol commented Oct 27, 2022

I like the cleanness of this proposal, but I think it can be a bit annoying when you start styling an element...

foo {
  color: blue;
}

and then realize you also want to style the descendants, so you have to refactor it all. With other proposals the change is simpler:

This Proposals 1 & 3 Proposal 4
-foo {
-  color: blue;
+@nest foo {
+  & {
+    color: blue;
+  }
+  & bar {
+    color: red;
+  }
 }
 foo {
   color: blue;
+  & bar {
+    color: red;
+  }
 }
 foo {
   color: blue;
+} {
+  & bar {
+    color: red;
+  }
 }

@plinss
Copy link
Member Author

plinss commented Oct 27, 2022

I think the refactoring is fairly minimal, you're just prepending @nest to the original selector and wrapping the exiting properties in {}s. The diff is making the changes appear to be more than they are.

I do have to note that your example is slightly wrong:
It's not:

+@nest foo {
+  & {
+    color: blue;
+  }
+  & bar {
+    color: red;
+  }
+}

but:

+@nest foo {
+  & {
+    color: blue;
+  }
+  bar {
+    color: red;
+  }
+}

(note no & on the nested rule).

The basic gist is that instead of a style rule that expects properties, we're minting a new kind of style rule that expects nested selectors. And then providing a carve-out for properties.

@Loirooriol
Copy link
Contributor

Just noting it's still a less local change, since probably the cursor is at the end of the declarations, and then you have to go back to the selector, select all the declarations to indent them, etc. Not a big problem but it's an annoyance for me.

note no & on the nested rule

Even if optional, I hope & is still allowed?

@plinss
Copy link
Member Author

plinss commented Oct 27, 2022

Even if optional, I hope & is still allowed?

No, not on a nested rule. The & in this proposal indicates that the following block is a collection of style properties that apply to the selector on the at-rule (and we could do this without the & entirely, in which case I wouldn't allow it as optional even there).

As I said, by default, everything inside a @nest is a style rule relative to the selector on the rule itself.

My original thought was to not even allow properties inside at all, e.g.:

foo {
  color: blue;
}
@nest foo {
  bar { color: red; }
}

but having to repeat the first selector seemed an unnecessary redundancy.

The main issue with all the other proposals is that style rules expect properties, and the syntax for properties has way too many collisions with selectors, so a carve-out for allowing selectors gets cumbersome.

This proposal makes a new kind of style rule that expects nested style rules, the carve-out is to allow properties within a limited scope.

Another detail that I didn't mention originally, by default there's an ancestor combinator between the @nest selector and nested selectors. There's no reason that the @nest selector couldn't end with a combinator, or the nested rules could start with a combinator (probably not both tho).

E.g.:

@nest foo > {
   bar { ... }
   baz { ... }
}

would be equivalent to:

foo > bar { ... }
foo > baz { ... }

and

@nest foo {
  > bar { ... }
  + baz { ... }
}

would be equivalent to:

foo > bar { ... }
foo + baz { ... }

@Loirooriol
Copy link
Contributor

Why disallow & to express complex relationships? I want to be able to use these:

@nest foo {
    &:hover { color: blue; }
    bar + & { color: red }
    & + & { color: green }
}

@tabatkins
Copy link
Member

the syntax for properties has way too many collisions with selectors, so a carve-out for allowing selectors gets cumbersome.

Note: it has one collision with selectors. The carve-out to avoid the ambiguity is rather simple, as Option 3 has shown.

No, [& is not allowed] on a nested rule.

It must be allowed, to hit a number of important use-cases, namely the &:hover and .parent & ones. Making it purely a suffix to the parent with a combinator between them is too limited.


But yeah, generally I agree with Oriol. This is a relatively invasive change to add nesting to an existing style rule. It continues to require non-local edits in deeper nesting, too, going from:

@nest foo {
  bar {color: red;}
}

to

@nest foo {
 @nest bar {
   & { color: red; }
   baz { color: green; }
  }
}

@plinss
Copy link
Member Author

plinss commented Nov 26, 2022

Ok, in my original thinking & in this proposal would only be used to denote a rule matching the selector in the @nest rule, but I can see the use cases for allowing it in other nested rules, especially for the pseudo-element usage.

So we can allow the & to appear (zero or one times) in any nested rule. If not present, it defaults to being at the front with a space combinator.

I disagree with the added indenting being a problem however, all modern code editors can indent and un-indent blocks with a single keystroke, and any decent diff tool will also clearly show changes as being whitespace-only. I don't see this a burden on developers (in fact, showing that something is now the root of a nesting context in a diff may actually be useful). I still think this proposal deserves consideration as it neatly side-steps all the other issues with the other nesting proposals.

@bramus
Copy link
Contributor

bramus commented Nov 29, 2022

This is a relatively invasive change to add nesting to an existing style rule. It continues to require non-local edits in deeper nesting

I agree with Tab here. Having to jump around in your code upon pasting a block to enable nesting does not seem feasible to me (-).

Only benefit I see this approach has over proposal 4, is that this one actually can nest the nested stuff (+), but that also requires a code adjustment in which you need to put the declarations in a nested & {} (-).

(Note: proposals 3 and 1 still have my preference, as indicated in the voting round we did here).

@atanassov atanassov changed the title [css-nesting-1] Yet another nesting proposal [css-nesting-1] Yet another nesting proposal (Option 5) Nov 30, 2022
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-nesting-1] Yet another nesting proposal, and agreed to the following:

  • RESOLVED: call this Option 5 :)
The full IRC log of that discussion <jensimmons> I'd like to begin this discussion by requesting we call this "Option 5".
<fantasai> RESOLVED: call this Option 5 :)
<fantasai> plinss: This is something I've been wanting to do since the 90s, and immediately ran into problem of conflicts when nesting selectors inside a rule that expects properties
<fantasai> plinss: I understand option 3 is trying to find workaround, an doption 4 a different approach
<fantasai> plinss: Wondering what if we turn the problem around entirely
<fantasai> plinss: and introduce a top-level construct that only contains nested rules
<fantasai> plinss: so never mix properties and rules in a single context
<fantasai> plinss: My concern with Option 3 is that even if we have a syntactic way to disambiguous, that does paint us into a corner
<fantasai> plinss: harder to introduce new things in the future
<fantasai> plinss: My proposal is to introduce an @rule, calling @nest
<fantasai> plinss: and inside that block is nothing but nested rules
<fantasai> plinss: Can have no selector for a block, and that applies properties to the top-level selector
<TabAtkins> q+
<fantasai> plinss: No parsing changes, no extra lookahead, no changes to OM, easy to explain, no special bizarre syntax rules, just works
<fantasai> plinss: Downside is you add an extra layer of indenting for properties that apply to top-level rule
<fantasai> plinss: but I don't see that as a problem because I add extra blocks to code all the time, modern tools make this easy
<fantasai> plinss: it's easy to see what happens
<oriol> q+
<fantasai> plinss: I think this is much better than adding cognitive load to authors, learning weird parsing exception rules
<fremy> q?
<Rossen_> ack TabAtkins
<oriol> q-
<fantasai> TabAtkins: As I said in issue, my objection is still that it requires a non-local edit
<fantasai> TabAtkins: Can't just add stuff, need to go back and change the existing rule to allow nesting
<fantasai> TabAtkins: It's a non-trivial bit of work, and is distinct from the way that every preprocessor has tried to do things since they invented nesting
<fantasai> TabAtkins: Also, I continue to think that any statement about complexity of the rules for Option 3 is overstated, it's literally just "does the selector start with an identifier? if so need a prefix"
<fremy> q+
<fantasai> TabAtkins: not that big a deal to learn or work around
<fantasai> TabAtkins: Doing non-local edits to add nested rules to something is also a cognitive load when making edits in place
<fantasai> TabAtkins: I don't like it
<fantasai> TabAtkins: it does have better qualities than some other proposals
<fantasai> TabAtkins: but it's roughly equivalent to just prepending @nest to all rules in terms of overall functionality, just changes where @nest shows up basically
<fantasai> TabAtkins: That's been mildly rejected by authors and WG already, would like to avoid
<fantasai> plinss: On the conflict issue, the point that you're missing is that mixing selectors and properties in one block does constrain us for future expansion
<fantasai> plinss: if we ever want to put [missed] in a property block, we can't do that
<fantasai> plinss: I'm very concerned about contraining our ability to expand CSS later
<fantasai> TabAtkins: I share your concern. Still have some room for expansion, so I'm okay with it. E.g. if your expansion is after property name, can still do that; or if we introduce functional notations, we can still do that
<fantasai> TabAtkins: neither of these would be parsed as a nested rule
<fantasai> TabAtkins: so think there's still enough space for expansion, in my opinion
<ydaniv> s/contraining/constraining/
<fantasai> plinss: Also, you can interleave properties and nested rules, how does that show up in the OM? Will lose that on reserialization
<fantasai> plinss: my proposal avoids because can't do that
<fantasai> TabAtkins: Assigning meaning to ordering seems fraught in first place, but it does mean when reserialize it'll look different from input
<Rossen_> q
<fantasai> plinss: no functional difference, but authors will order things for organizational reasons, and it's a loss to the author if lost on reserializatoin
<astearns> In option 5 you can still interleave, I think `@nest foo { & {} bar {} & {} }`
<fantasai> fremy: I feel like there's a way of serializing this
<fantasai> fremy: I don't agree that if you allow properties after rules it's meaningless
<fantasai> fremy: it's same as selector with & only
<fantasai> fremy: It affects ordering
<fantasai> fremy: If your property defined after a rule doesn't work
<fantasai> fremy: but that's a different topic, but it's something to consider
<plinss> astearns, yes, but the ordering is preserved because they're all style rules
<astearns> ok
<fantasai> fremy: Other than that, I feel like one positive point from plinss's proposal he didn't mention
<fantasai> fremy: Shared with Option 4
<fantasai> fremy: It's ability to copy / paste
<fantasai> fremy: If you nest with Option 3, cannot copy paste without running into problems maybe
<fantasai> fremy: Both this and option 4 have this ability
<fantasai> fremy: I agree that when you change from one rule to nesting, you have t oadd some stuff before/after, but I think it's worth mentioning that for Option 3 that if you go from nesting to not nesting or vice versa
<fantasai> fremy: you need to change things
<fantasai> fremy: refactoring is a pain in both cases, but it's different kind of paint
<fantasai> fremy: but I wanted to mention this important point
<tantek> copypaste++ and and also +1 for adding/removing nesting without having to rewrite the contents as a design principle
<Rossen_> q?
<argyle> q+
<Rossen_> ack fremy
<jensimmons> +q
<fantasai> argyle: I'm confused by the copy/paste scenario? I'm writing CSS using nesting 1, 2, 3,
<fantasai> argyle: I copy paste stuff in and out of socpe, in and out of ..., just use & everywhere
<fantasai> fremy: You decided to use & everywhere, but if you remove nesting it'll break
<Rossen_> ack argyle
<fantasai> argyle: it only breaks with :has()
<fantasai> fremy: that's not what I mean, what I mean is if you have a CSS file with a lot of rules, they can start with any selector
<fantasai> fremy: html or p or whatever
<fantasai> fremy: If you want to take all these rules and nest them, and say they only apply in this div with special ID
<fantasai> fremy: you have to add & to selectors or they will break
<fantasai> fremy: You can't just copy paste them into the brackets
<fantasai> argyle: Concern in general, & makes it better and more clear
<fantasai> fremy: Even if it was required, you would have to add these when you paste into nested context
<fantasai> fremy: When you have a stylesheet with normal selectors, if you want to nest this stylesheet, you have to add & before every selector or it won't work
<fantasai> argyle: I would like to see examples
<fremy> stylesheet was " html { color: red }
<fantasai> Rossen_: Sounds like some conversation paste each other, but 2-3 line example from fremy you will be able to see what he means about usability
<fremy> if you want to nest this in #id
<fremy> "#id { html { color: red } }" is not valid
<fantasai> argyle: I've been using this stuff, it's not just theoretical
<fantasai> argyle: I have 100s of lines of nested lines of CSS, I don't find a portability issue
<fremy> you have to go and edit "#id { & html { color: red } }"
<fantasai> argyle: I don't see what you're talking about
<Rossen_> q?
<fremy> and you have to do that for potentially a lot of selectors
<fantasai> argyle, you're one of 50% of authors who think it's fine to prefix all their style rules everywhere forever with &. The other 50% of authors don't want to do this.
<argyle> i'm in both camps, trying both..
<fantasai> jensimmons: I really like this option, I like this a lot. I like it better than Option 4
<fantasai> jensimmons: what I would hope is that folks who've done a lot of work on this, and advocating to make this reality
<fantasai> jensimmons: I hope that you are honestly willing to consider these other possibilities
<fantasai> jensimmons: what's interesting about this is that it's a deviation from how web developers have thought about nesting from e.g. sass
<fantasai> jensimmons: it is more like writing an @rule and doing stuff inside it, isntead of having the shape from sass
<fantasai> jensimmons: but I don't think we should reject out of hand
<fantasai> jensimmons: there is something elegant of it
<miriam> q+
<fantasai> jensimmons: and I thinkw e ened to think of the full range of ppl who write CSS, from students to hobbyists to professionals that write lots of JS to professionals that do other type of software engineering to professionals that do [msised]
<fantasai> jensimmons: want it to be not breaking
<fantasai> jensimmons: make it easy to understand
<fremy> q?
<fantasai> jensimmons: think this proposal is very elegant and straightforward
<fantasai> miriam: 2 things
<fantasai> miriam: 1. This proposal is very much like @scope, doesn't have scoping aspects but otherwise the shape of it is basically identical to socpe
<fantasai> miriam: I don't know if that's positive or negative, but worth pointing out
<fantasai> miriam: I agree with Jen about who we should think about, but also with Tab about how this seems problematic
<fantasai> miriam: copy/paste is pitched as an advantage, but you're not abile to copy/paste int osomething not @nest, takes a lot of adjustment
<fantasai> miriam: All of these proposals have tradeoffs, and we keep fighting about which tradeoffs, and aruging which are better for authors
<Rossen_> ack jensimmons
<Rossen_> ack miriam
<fantasai> miriam: I think all of theme are hard for authors, but we need to pick one
<fantasai> fremy: I thought we agreed to make a survey and see what ppl think, but we now have another proposal
<fantasai> fremy: but we should probably should do that
<fantasai> Rossen_: Agreed
<fantasai> Rossen_: getting away from topic which is review of this proposal
<fantasai> Rossen_: appreciate plinss for describing the proposal and its pros/cons wrt others
<jensimmons> q+
<fantasai> Rossen_: My hope is that we'll get to next step and at some point will need to close the door on more options
<fantasai> Rossen_: and start scoping down which we will go with, which will work best for authors
<fantasai> Rossen_: with that, I want to move on...
<fantasai> Rossen_: but I want to simply take the conversation down to a closure with next steps being, let's figure out what ppl think about these options in a representative way
<fantasai> Rossen_: and then come back to making some choices
<fantasai> jensimmons: I would really love for us to come to a decision, a final decision, by the end of the year. That might be a little ambitious
<fantasai> jensimmons: I do think we're close.
<fantasai> jensimmons: we could just decide on Option 3 and call it a day, but I think we do want to have a bit more debate about 4 or 5
<fantasai> jensimmons: but I also hear the clock ticking, so would like a decision by early January
<fantasai> Rossen_: Appreciate pressure and urgency, and hope we'll have something end of year or beginning of next
<fantasai> Rossen_: Ok, let's wrap up this conversation. Thanks plinss for bringing this up
<fantasai> Rossen_: one last closing question, do we have a path forward to organize non-biased survey, and who would that be?
<fantasai> Rossen_: jensimmons, can you do that? You seem most non-biased :)
<fantasai> fremy: I think last time miriam, TabAtkins, fantasai and leaverou agreed to help
<fantasai> plinss: I can edit the new proposal into the table
<fantasai> -> https://drafts.csswg.org/css-nesting-1/proposals
<fantasai> ACTION: plinss to update proposals table
<fantasai> ACTION: jensimmons, miriam, leaverou, fantasai, etc. to create survey

@romainmenke
Copy link
Member

romainmenke commented Nov 30, 2022

do we have a path forward to organize non-biased survey

I think the bigger issue is author preconception.
The last survey result was basically : "we want what we have in Sass or as close to that as possible" The option that best fit that description was option 1 at the time.

@LeaVerou's Twitter polls again confirmed that authors want nesting to be as terse and relaxed as possible.

If the concern is to pick the right syntax for the future regardless of what preprocessors have done in the past then I don't think another author survey will be helpful. Authors will pick proposal 3. I don't think it is possible to design a survey for todays authors that will have a different outcome.


I personally would be fine with proposal 1, 3 or even 5.
But I am part of the minority that likes to read/write & and @nest.

@mirisuzanne
Copy link
Contributor

mirisuzanne commented Nov 30, 2022

I think @romainmenke is very likely right about predicting the poll result. But I guess we'll see.

I very much appreciate having various alternative proposals to consider, and have backed several of them at this point. But I disagree with the premise mentioned a few times that some proposals are more 'programmer focused' while others are more appropriate for a broad CSS audience. So far all the seriously-considered proposals have been designed around CSS syntax, and they have all involved awkward tradeoffs that authors will need to get used to. This isn't a philosophical debate about the soul of CSS, it's a practical debate about how to make nesting work around some inconvenient parsing limitations.

Anyway, I want to follow-up a little on my mention of @scope on the call. I said I wasn't sure if that's a pro or a con for this proposal, but I don't think that's actually the right framing. More important than that, it brings into clearer focus how these features overlap, and where they differ. That's a pro for the discussion, no matter where we land.

The overlap between @scope and @nest (version 5)

Like I said, the outline of this proposal (v5!) has a nearly identical footprint to the @scope feature as specified. These would select exactly the same elements:

@nest foo {
    & { color: blue; }
    bar { color: red; }
}

@scope (foo) {
    & { color: blue; }
    bar { color: red; }
}

The differences

There's only one difference between the selectors above, and that's scope proximity - which gives some cascade priority to scoped selectors that have a 'closer' scope root. It's not clear at this point how much of a difference that creates, because the spec is currently indecisive on how much priority scope proximity should have (@fantasai and I drafted the spec, and we disagree on that point). From my perspective a weak scope proximity (weaker than specificity in the cascade) would be a desirable addition to nesting. The difference is subtle, but meaningful to authors. If we went that rout, the two features have a very large overlap.

But it's not a complete overlap, as we can see by looking at one of the other demos in the original post. The @scope rule only targets elements that are inside the scope root (inclusive of the root element itself). So any nested rules that aren't truly nested may be an issue:

@nest foo {
  + baz { /* sibling selector should work here, and match foo + baz */ }
}

@scope (foo) {
  + baz { /* since baz is a sibling of the scope root, it is outside the scope and not matched */ }
}

That's a pretty big distinction.

(There are also big distinctions on the other end - where @scope allows adding lower boundaries - but that's less relevant here. The main point is to say they have overlap, but they are not the same feature.)

Can we disentangle these features?

It seems to me like:

  • In most (all?) cases where you want to select a truly nested element, @scope might be the better alternative. That's exactly what it's designed for.
  • Most (but sadly not all) of the problematic edge cases for other nesting proposals fall into that category.

These problem cases for proposal 3 can all be represented in @scope without issues - because they target elements that are in scope:

foo {
  baz { /*…*/ }
  html & { /*…*/ }
  baz + & { /*…*/ }
}

@scope (foo) {
  baz { /*…*/ }
  html & { /*…*/ }
  baz + & { /*…*/ }
}

My thesis here is roughly that @scope would help limit the frequency of problematic use-cases. And it will do that in a way that matches this proposal pretty closely.

Sadly for my thesis, it's not quite all the problematic cases. These can't be converted to scope, because the targets are not nested:

foo {
  baz:has(&) { /*…*/ }
  baz > & + bing { /*…*/ }
}

I don't feel like that's a conclusive argument. But, I think the main takeaway here is that we need to keep thinking of these features as intertwined. Specifically, the implementation of scope might change what people need or want from nesting.

@plinss
Copy link
Member Author

plinss commented Nov 30, 2022

One other advantage to this proposal, @nest is something that authors can enter into a search engine when they encounter it in the wild for the first time. (It also provides context to figure it out on their own.)

An author seeing:

foo {
  color: red;
  & bar { color: blue; }
}

or

foo {
  color: red:
} {
  bar { color: blue; }
}

has no keywords any search engine is going to provide reasonable results for (and no obvious indication that nesting is happening, either could be interpreted as typos).

@LeaVerou
Copy link
Member

LeaVerou commented Dec 1, 2022

As an author, I would be strongly against something that requires me to create a whole separate rule like that. Part of the benefit of nesting is organizing my code into portable "chunks", so I can copy a rule and have all the CSS needed for a "component". It seems that option 5 breaks this, unless I'm missing how exactly it's supposed to be used.

@LeaVerou
Copy link
Member

LeaVerou commented Dec 1, 2022

One other advantage to this proposal, @nest is something that authors can enter into a search engine when they encounter it in the wild for the first time. (It also provides context to figure it out on their own.)

An author seeing:
[snip]
has no keywords any search engine is going to provide reasonable results for (and no obvious indication that nesting is happening, either could be interpreted as typos).

CSS is full of symbols that have the same issue: #id, .class, [foo] etc
Also, googling "ampersand css" would probably work fine (as evidenced by the current results which are about nesting in preprocessors).

@plinss
Copy link
Member Author

plinss commented Dec 1, 2022

As an author, I would be strongly against something that requires me to create a whole separate rule like that. Part of the benefit of nesting is organizing my code into portable "chunks", so I can copy a rule and have all the CSS needed for a "component". It seems that option 5 breaks this, unless I'm missing how exactly it's supposed to be used.

I don't understand your point here at all. All the CSS for a "component" is within the @nest rule and it can be copied as a whole. If anything, by not requiring & everywhere you're making your CSS more copyable and usable in other contexts. Can you give an example of what you're concerned about? (and how that would be different from the other proposals?)

@plinss
Copy link
Member Author

plinss commented Dec 1, 2022

CSS is full of symbols that have the same issue: #id, .class, [foo] etc

Doesn't mean we're doing people any favors by adding more... (in fact, there's even a TAG design principle about this :-)

Another point, how would proposals 3 & 4 be feature detectable? (I accept it can likely be done, but in an obvious way?)

@LeaVerou
Copy link
Member

LeaVerou commented Dec 1, 2022

CSS is full of symbols that have the same issue: #id, .class, [foo] etc

Doesn't mean we're doing people any favors by adding more... (in fact, there's even a TAG design principle about this :-)

My point wasn't "there is precedent for this bad thing and thus it's fine", it was "CSS already does this and authors don't seem to have any trouble googling things". So I don't think the TAG principle you linked applies here. Learnability is one component of usability, efficiency is another, equally important one. For commonly typed things, brevity becomes more important than learnability.

Another point, how would proposals 3 & 4 be feature detectable? (I accept it can likely be done, but in an obvious way?)

@supports selector(&)?

@plinss
Copy link
Member Author

plinss commented Dec 1, 2022

My point wasn't "there is precedent for this bad thing and thus it's fine", it was "CSS already does this and authors don't seem to have any trouble googling things". So I don't think the TAG principle you linked applies here. Learnability is one component of usability, efficiency is another, equally important one. For commonly typed things, brevity becomes more important than learnability.

CSS has rejected other uses of sigils in the past for precisely the searchability issue, and it's not necessarily obvious to an author to search for 'ampersand' instead of &. Brevity can be taken too far (see YAML), it is a tradeoff, but not an obvious one. And given that @nest mostly doesn't require the & it can easily result in less typing if there are enough nested rules, so proposal 3 doesn't necessarily win on brevity.

@supports selector(&)?

Maybe, but seems fragile, what if there are other places & can be used? (and is it really a selector?)

@LeaVerou
Copy link
Member

LeaVerou commented Dec 1, 2022

CSS has rejected other uses of sigils in the past for precisely the searchability issue, and it's not necessarily obvious to an author to search for 'ampersand' instead of &. Brevity can be taken too far (see YAML), it is a tradeoff, but not an obvious one. And given that @nest mostly doesn't require the & it can easily result in less typing if there are enough nested rules, so proposal 3 doesn't necessarily win on brevity.

Yes, because to use a symbol, the feature needs to be needed frequently enough that brevity is important. This is not true for every feature, but it certainly is for nesting.

@supports selector(&)?

Maybe, but seems fragile, what if there are other places & can be used?

Other places …in a selector?

(and is it really a selector?)

Yes, it is. Really.

@plinss
Copy link
Member Author

plinss commented Dec 1, 2022

Yes, because to use a symbol, the feature needs to be needed frequently enough that brevity is important. This is not true for every feature, but it certainly is for nesting.

And in this proposal, even the & is not going to be needed that often (zero characters wins for brevity every time). The enclosing @nest will likely not get repeated enough to justify its removal/replacement.

Other places …in a selector?

Yes, in principle it could be used in a selector in some context that isn't nesting. We have a limited number of ascii-friendly sigils to use and may need to repurpose it at some point. If the need is to detect the feature of nesting (or the other theoretical usage), it may not be enough to disambiguate. Being able to detect an actual at-rule is more specific, and more obvious.

You also still never explained your initial objection about having to create a "whole separate rule" and the perceived componentization issue.

@bramus
Copy link
Contributor

bramus commented Dec 1, 2022

Below is a slightly altered version of this example from the spec (the difference being table.colortable td .c instead of table.colortable td.c):

table.colortable td {
  text-align:center;
}
table.colortable td .c {
  text-transform:uppercase;
}
table.colortable td:first-child, table.colortable td:first-child+td {
  border:1px solid black;
}
table.colortable th {
  text-align:center;
  background:black;
  color:white;
}

Converted to syntax 5 it becomes this:

(UPDATE: Updated the code to remove the unnecessary @nest as per suggestion. Kept the & { } block though, but you can omit the & itself from it if you want):

@nest table.colortable {
    @nest td {
        & {
            text-align: center;
        }
        .c {
            text-transform: uppercase;
        }
        &:first-child,
        &:first-child + td {
            border: 1px solid black;
        }
    }
    th {
        text-align: center;
        background: black;
        color: white;
    }
}

Actions that were needed:

  • I need to prepend @nest to parent selectors to enable nesting upon adding a nested selector. This moves my focus away from the original code I pasted in.
  • In the existing ruleset, I need to take the existing declarations and wrap them inside a & { } upon adding a nested selector.
  • To add components to the parent selector, I need to prepend &

While doing this:

  • I found myself jumping around the code quite a bit, which is not something I liked.

  • I didn’t find this to be highly copy-paste friendly. When I copy a ruleset from somewhere else in my stylesheet to target a child element of table.colortable td .c, I need to make the first two of the three changes listed above:

    @nest table.colortable {
        @nest td {
            & {
                text-align: center;
            }
            @nest .c {
               & {
                  text-transform: uppercase;
               }
               span {
                  color: hotpink;
               }
            }
            &:first-child,
            &:first-child + td {
                border: 1px solid black;
            }
        }
        th {
            text-align: center;
            background: black;
            color: white;
        }
    }

Furthermore, with this fifth proposal, I noticed:

  • Declarations are sometimes in a & { } block and sometimes not. This is not consistent.
  • Selectors are sometimes prefixed with @nest and sometimes not. This feels redundant, as the intent to nest was already there when I pasted in the nested ruleset.

Comparing this to proposal 3:

  • There I can paste it my copied content, and maybe need to add an & , depending on the selector.
    • Furthermore: as there’s no harm in always adding the & , I don’t really need to worry about this maybe and can simply always add it if I wanted.
  • Declarations are consistent, as long as they come first.
table.colortable {
    & td {
        text-align: center;
        .c {
            text-transform: uppercase;
            & span {
              color: hotpink;
           }
        }
        &:first-child,
        &:first-child + td {
            border: 1px solid black;
        }
    }
    & th {
        text-align: center;
        background: black;
        color: white;
    }
}

This last code example –using proposal 3– is imo much more consistent than the proposal 5 version.

@plinss
Copy link
Member Author

plinss commented Dec 1, 2022

A few corrections:

  1. all of the &s in your example except for &:first-child are unnecessary, legal, but they are the default behavior.
  2. the last @nest th { { is also legal, but completely unnecessary. That could simply be th { text-align: ...}

Remember, in this proposal @nest the the thing that contains nested rules, not a nested rule itself (unless it's a second level of nesting).

So it's actually:

@nest table.colortable {
    @nest td {
        {
            text-align: center;
        }
        .c {
            text-transform: uppercase;
        }
        &:first-child,
        &:first-child + td {
            border: 1px solid black;
        }
    }
    th {
        text-align: center;
        background: black;
        color: white;
    }
}

@bramus
Copy link
Contributor

bramus commented Dec 1, 2022

A few corrections

Thanks for clarifyin. Updated the code to remove the unnecessary @nest. Kept the & { } though, but added a note that the & itself can be removed.

@LeaVerou
Copy link
Member

LeaVerou commented Dec 1, 2022

Yes, because to use a symbol, the feature needs to be needed frequently enough that brevity is important. This is not true for every feature, but it certainly is for nesting.

And in this proposal, even the & is not going to be needed that often (zero characters wins for brevity every time). The enclosing @nest will likely not get repeated enough to justify its removal/replacement.

In option 3, which is the current state of the spec, & will still be needed pretty often. It's common to style descendant element selectors.

Other places …in a selector?

Yes, in principle it could be used in a selector in some context that isn't nesting. We have a limited number of ascii-friendly sigils to use and may need to repurpose it at some point. If the need is to detect the feature of nesting (or the other theoretical usage), it may not be enough to disambiguate. Being able to detect an actual at-rule is more specific, and more obvious.

I still don't understand what problem you are seeing. You think browsers will implement & in selectors, without implementing Nesting, and we need a way to distinguish the two?

You also still never explained your initial objection about having to create a "whole separate rule" and the perceived componentization issue.

Copying two rules (two sets of {...}) is more error-prone than copying one and feels more scattered.

@astearns
Copy link
Member

astearns commented Dec 5, 2022

I think option 5 has an issue with progressive enhancement.

With options 3 and 4 I can write rules for the nesting root, then add on a nesting block. Then, for browsers that do not support nesting I have to repeat the nesting block in an @supports not ??? rule (where ??? is whatever syntax we choose for detecting nesting).

But with option 5 I have to repeat both the nesting root and the nesting block in my @supports not ??? rule, because older browsers will ignore the @nest rule entirely.

Not a big issue long-term, but annoying for the transition.

@plinss
Copy link
Member Author

plinss commented Dec 5, 2022

Yes, you'd have to repeat the outer rule.

But, the point of nesting is to make the stylesheet smaller and more maintainable. It completely defeats the purpose to have an @supports block in the same stylesheet to also include non-nested versions. With any of the proposals, the best way to support older browsers is to write your stylesheet with nesting, and then use a pre-processor to generate a non-nested version, then use the @supports mechanism to import either the more modern or the older (and larger) stylesheet when necessary.

@vrubleg
Copy link

vrubleg commented Dec 5, 2022

Instead of @nest selector { /* nested rules */ }, a bit shorter selector {{ /* nested rules */ }} could be used for the same purpose.

@plinss
Copy link
Member Author

plinss commented Dec 5, 2022

Unfortunately that would require arbitrary lookahead during parsing.

It also gets weird when one of the legal contents is a style rule without a selector, so you'd get:

selector {{{ color: red; } selector { color: blue; }}}

Not sure that helps readability, maintainability, or searchabilty.

@vrubleg
Copy link

vrubleg commented Dec 5, 2022

Unfortunately that would require arbitrary lookahead during parsing.

{{ and }} shouldn't allow spaces inside double braces. If a parser encountered {{, it means that it is a nested block.

{{{ should not be allowed.

selector {{
    & { /* properties */ }
    /* other nested rules */
}}

This should be used instead if required.

@plinss
Copy link
Member Author

plinss commented Dec 5, 2022

{{ and }} shouldn't allow spaces inside double braces. If a parser encountered {{, it means that it is a nested block.

  1. That doesn't remove the lookahead, the parser can't tell if it's parsing a regular style rule or a nesting container until it finds the {{. They are completely different objects in the OM so the parser needs to know ASAP. The selector can be arbitrarily long (and even have multiple selectors separated by ,s), current CSS parsers don't require more than a handful of token lookaheads and we try to not add more, especially an arbitrary amount.
  2. That effectively introduces {{ and }} as new sigils, either through a parsing change or a one-off rule.

{{{ should not be allowed.

Why not? The & is optional, requiring a space after the opening {{ is arbitrary. We generally don't require specific formatting in CSS. This is just introducing more arbitrary rules that authors will have to learn. One of the main points of this proposal is to remove those as found in the other proposals. Another is to avoid parser changes. Yet another is to make the feature obvious and searchable.

You're adding a bunch of complexity and author pain to save 4 characters.

@sesse
Copy link
Contributor

sesse commented Dec 6, 2022

One other advantage to this proposal, @nest is something that authors can enter into a search engine when they encounter it in the wild for the first time.

If you search for “& css”, the first top hits are about CSS nesting (in SASS).

@sesse
Copy link
Contributor

sesse commented Dec 6, 2022

  1. That doesn't remove the lookahead, the parser can't tell if it's parsing a regular style rule or a nesting container until it finds the {{. They are completely different objects in the OM so the parser needs to know ASAP.

FWIW, despite everything that I don't like about this proposal, lookahead wouldn't be a problem for Blink here from what I can see. We know we're parsing a selector, so we don't have to backtrack when we see the {{ (we just have to use the selector for “something else” instead of a regular style rule).

@fantasai
Copy link
Collaborator

Closing as WONTFIX by CSSWG resolution see minutes

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

No branches or pull requests