Closed LeaVerou closed 4 months ago
Thank you @LeaVerou.
As a design principle, I don't think CSS should have author-facing syntax that provides no benefit to authors. Either we should come up with actual use cases for
@nest
I actually want the @nest
rule but as a more generic organizational tool.
In JavaScript it is possible to write blocks without any condition. I use this very frequently to group bits of code in a larger section.
This new at-rule could serve a similar purpose. Most authors will never use it but they could to organise and structure their code.
Today we use either multiple rules with the same selector, or comments when we want to group lists of declarations. Neither is really good.
This is our list of custom props when we start a new project :
:root {
/* #region Colors */
--color-foreground: rgb(0 0 0);
--color-background: rgb(255 255 255);
--color-grey-40: rgb(108 112 108);
--color-warning: rgb(242 149 0);
--color-error: rgb(207 0 0);
/* #endregion Colors */
/* #region Typography */
--font-sans: "Helvetica Neue", sans-serif;
--font-serif: "Georgia", serif;
/* #endregion Typography */
/* #region Spacers */
--space-1--px: 1px;
--space-1--rem: 0.0625rem;
--space-8--px: 8px;
--space-8--rem: 0.5rem;
--space-16--px: 16px;
--space-16--rem: 1rem;
--space-32--px: 32px;
--space-32--rem: 2rem;
--space-48--px: 48px;
--space-48--rem: 3rem;
/* #endregion Spacers */
}
We need to add comments before and after each group so that we can have folding regions in code editors.
@tabatkins has also stated that they aren't attached to the name of this at-rule. Something that isn't tied to nesting would be nice.
If on the other hand, this can all be implementation details, then I prefer that. I however fear that we can only kick this bucket further down the road and that some API will surface the warts.
In JavaScript it is possible to write blocks without any condition. I use this very frequently to group bits of code in a larger section.
@tabatkins has also stated that they aren't attached to the name of this at-rule. Something that isn't tied to nesting would be nice.
Do we need the @nest
part of the rule? Can we use naked {}
? Looking at css-syntax, I don't immediately see why not. In other words:
:root {
/* #region Colors */
{
--color-foreground: rgb(0 0 0);
--color-background: rgb(255 255 255);
--color-grey-40: rgb(108 112 108);
--color-warning: rgb(242 149 0);
--color-error: rgb(207 0 0);
}
/* ... etc ... */
}
fixing the specifics is more web compatible than changing the current behavior after even longer.
To be clear, this issue blocks actually carrying out https://github.com/w3c/csswg-drafts/issues/8738. The more we mess around with the details here, the higher the risk that no change will happen at all.
To be clear, this issue blocks actually carrying out #8738. The more we mess around with the details here, the higher the risk that no change will happen at all.
That was not the resolution at all. Please implement the @nest
rule as currently specced by @tabatkins. It is far, far easier to change the details of that than to change the current behavior of nesting later. That's the whole point of the resolution we made on Wednesday!
@LeaVerou Oh. I thought the point of the resolution was to focus the discussion and hopefully converge on something.
Unfortunately I can't make a move on @nest
until this issue is resolved. I will likely not be able to ship a breaking change with the intent to break it again later, especially when there's no consensus on what the follow-up change would be.
It is far, far easier to change the details of that than to change the current behavior of nesting later.
The specific changes we're discussing here can not necessarily be made later at all. It is sometimes very hard to understand the risk of changing CSSOM APIs.
@andruud It's not with the "intent to break it again later". It's with the intent to keep @nest
as a better option than the "shifting up", but leaving the door open to explore other possibilities.
The specific changes we're discussing here can not necessarily be made later at all.
If later on we prefer to switch to something else but it's not compatible, then so be it, we can stay with @nest
.
The point is that there was consensus that @nest
is better than "shifting up". Exploring other possible approaches shouldn't block @nest
, since delaying it will just make it not compatible.
As I said in my final comment in 8738, when considering any new feature, or additional complexity in an existing feature, it's worth paying attention to both the size of the affected audience and the size of the benefit that audience will receive.
The existence of an @nest
rule is visible solely to authors who are crawling the CSSOM, or serializing the OM and then looking at it. Both of these are advanced use-cases, they're not something that 99% (and I think that's a very conservative percentage) of authors will ever do. I, myself, have never inspected the CSSOM in my entire webdev career except when writing WPTs.
So, the audience of people affected by any change here is very tiny, I'll argue, and that audience is composed of relatively skilled authors. (I think this audience can be reasonably summarized as "people who are writing CSS tooling".)
Do any of these changes allow that audience to do something currently difficult or impossible? No, these are solely attempting to improve the consistency of how the OM is represented in certain aspects.
Do they improve this audience's experience in a meaningful way? Arguable. While they improve the consistency in some ways (making the OM and/or the reserialized string more closely resemble the originally authored code), they reduce consistency in other ways (unusual serialization rules; edge cases that require restrictions on where rules can be moved around; widening of the possible types in certain attributes).
For example, right now you can, in theory, take any of the objects in an MQ's .cssRules
list and move it out of that MQ, appending it to the stylesheet itself. If the object here is a CSSStyleDeclaration, what happens? Is that an error? If it's a CSSNestRule, but we have special serialization rules that make it look like raw declarations, how does it serialize in this case? (Or, again, maybe it causes an error?)
Or take .insertRule()
. Its first argument is a string, which is parsed to obtain a rule to insert. (You can pass a rule object directly, but it's serialized and then re-parsed.) If you can't write @nest
directly in a stylesheet, how do you insert such a rule? Do we have to now accept code like x.insertRule("color: blue;")
, which is invalid today?
Or a parsed stylesheet will never have an @nest
at the start of a style rule, or next to each other. But via CSSOM manipulation you can construct such situations. How do they serialize? Will you still end up with a stylesheet that parses into a different OM than it started from
Whatever we do, if it's "magic" in some way, it'll spawn corner cases like this. Magic like this can be justified, by sufficient benefit to a sufficient audience. I don't think that it qualifies on either metric in this case, however.
In summary, we should just do the simplest possible thing, with the least amount of magic possible, because all of the suggested magic (beyond the initial parser magic) won't actually pay for itself.
Do we need the @nest part of the rule? Can we use naked {}? Looking at css-syntax, I don't immediately see why not.
We could, it would just be a new syntax construct to define. Relying on the existing at-rule syntax just makes for a slightly simpler model. And since we don't anticipate authors actually writing this themselves, I don't think the length of the name actually matter in any meaningful way.
@andruud
@LeaVerou Oh. I thought the point of the resolution was to focus the discussion and hopefully converge on something.
Unfortunately I can't make a move on
@nest
until this issue is resolved. I will likely not be able to ship a breaking change with the intent to break it again later, especially when there's no consensus on what the follow-up change would be.It is far, far easier to change the details of that than to change the current behavior of nesting later.
The specific changes we're discussing here can not necessarily be made later at all. It is sometimes very hard to understand the risk of changing CSSOM APIs.
We are trying to avoid a breaking change by shipping @nest
in some shape or form. @tabatkins is arguing right under your comment that how we implement this in the OM only affects a tiny fraction of authors. You are saying that it would be a breaking change and that "it’s very hard to understand the risk of changing CSSOM APIs". It may be productive if you two have a breakout to align on this, since you work at the same company? Because unless I’m missing something, I don't see how both statements can be true (it is both impactful and not impactful).
@tabatkins
As I said in my final comment in 8738, when considering any new feature, or additional complexity in an existing feature, it's worth paying attention to both the size of the affected audience and the size of the benefit that audience will receive.
Tab, nobody is disagreeing that I/E matters, we’re disagreeing on what the I is.
The existence of an
@nest
rule is visible solely to authors who are crawling the CSSOM, or serializing the OM and then looking at it. Both of these are advanced use-cases, they're not something that 99% (and I think that's a very conservative percentage) of authors will ever do. I, myself, have never inspected the CSSOM in my entire webdev career except when writing WPTs.
I have responded to this point in the OP:
I strongly disagree with Tab that reading and modifying the CSS OM only affects about a hundred developers in the entire world (!). Reading and modifying the CSS OM is at the core of a ton of very widespread libraries (e.g. jQuery, D3, etc). Even if most authors do not use it directly, I assure you almost every developer reads and modifies the CSS OM regularly, it's just done through several layers of abstractions.
Also, since we’re sharing anecdotes, I have crawled and modified the CSS OM dozens of times in my web dev career, none of which was while writing WPTs. I did not mention it earlier because as an anecdote, I didn’t think it was that relevant.
So, the audience of people affected by any change here is very tiny, I'll argue, and that audience is composed of relatively skilled authors. (I think this audience can be reasonably summarized as "people who are writing CSS tooling".)
Again, the users of these tools are not a small audience.
For example, right now you can, in theory, take any of the objects in an MQ's
.cssRules
list and move it out of that MQ, appending it to the stylesheet itself. If the object here is a CSSStyleDeclaration, what happens? Is that an error? If it's a CSSNestRule, but we have special serialization rules that make it look like raw declarations, how does it serialize in this case? (Or, again, maybe it causes an error?)
I don’t see how this is different than defining what @nest {}
does in the root scope. We’ll have to define something however you look at it. And if anything, defining what root-level declarations do is actually quite useful (anecdotally, I’ve written special code to handle raw declarations at the root level several times when coding CSS demos).
Actually, depending on how we define it root-level declarations could even solve all these use cases that require setting stuff on the document node.
Or take
.insertRule()
. Its first argument is a string, which is parsed to obtain a rule to insert. (You can pass a rule object directly, but it's serialized and then re-parsed.) If you can't write@nest
directly in a stylesheet, how do you insert such a rule? Do we have to now accept code likex.insertRule("color: blue;")
, which is invalid today?
Yes! Which again seems like a win and more broadly useful.
Or a parsed stylesheet will never have an
@nest
at the start of a style rule, or next to each other. But via CSSOM manipulation you can construct such situations. How do they serialize? Will you still end up with a stylesheet that parses into a different OM than it started from
I’m not married to the details, but I was envisioning that these nodes would be merged automatically by the CSS OM, not during serialization. All the Web Platform’s OMs are full of these kinds of normalizations, so I don’t see what the problem is.
The users of tools that crawl/modify the CSSOM only matter for this proposal insofar as those tools expose the CSSOM to them (or this proposal makes those tools work better, thus improving the experience of their users). Do you have examples of that?
I don’t see how this is different than defining what @nest {} does in the root scope.
Sure, we have to define what @nest
does in the global scope, in exactly the same way we defined what &
matches in the global scope. (And it's currently defined to act exactly the same.) But these additional things are more corner cases to define.
Yes! Which again seems like a win and more broadly useful.
How is this a meaningful win over x.insertRule("@nest { color: blue; }")
? I mean, it's very slightly shorter, sure. But does it matter otherwise? Is it worth the consistency loss of insertRule()
no longer being restricted to taking rules?
I’m not married to the details, but I was envisioning that these nodes would be merged automatically by the CSS OM, not during serialization. All the Web Platform’s OMs are full of these kinds of normalizations, so I don’t see what the problem is.
I don't think that's accurate to say. For example, in the following:
<!DOCTYPE html>
<p>
<script>
var p = document.querySelector("p");
p.appendChild(new Text("foo"));
p.appendChild(new Text("bar"));
console.log(p.childNodes);
</script>
You'll get two Text nodes, despite that being essentially identical to just having a single Text node with "foobar". Having nodes magically merge underneath you as a side-effect of doing something unrelated (like removing a rule that previously happened to separate two of them) is the sort of thing that causes bugs in author code very easily, since objects disappear, indexes change, etc. in an unpredictable manner.
Do we need the @nest part of the rule? Can we use naked {}? Looking at css-syntax, I don't immediately see why not.
We could, it would just be a new syntax construct to define. Relying on the existing at-rule syntax just makes for a slightly simpler model. And since we don't anticipate authors actually writing this themselves, I don't think the length of the name actually matter in any meaningful way.
Yeah, sure. {}
was a response to @romainmenke's world where authors might actually want to write it themselves. If @nest{}
basically does nothing (except add structure), perhaps {}
is a better way to represent it (and more in line with other languages).
Because unless I’m missing something, I don't see how both statements can be true (it is both impactful and not impactful).
That is not what I'm saying. I'm saying it's not always easy to prove that CSSOM changes won't break sites, even if you strongly believe it won't.
But OK, if shipping @nest
ASAP is indeed what everyone wants here, I can try. I'll need positions from Apple and Mozilla. (EDIT: https://github.com/mozilla/standards-positions/issues/1013, https://github.com/WebKit/standards-positions/issues/337).
If we have author facing syntax for this, I'd prefer it's not {}
so we can reserve {}
for something that is actually useful (of course this changes if we can figure out a way to make @nest {}
useful).
But OK, if shipping
@nest
ASAP is indeed what everyone wants here, I can try. I'll need positions from Apple and Mozilla. (EDIT: mozilla/standards-positions#1013, WebKit/standards-positions#337).
Thank you!
One question that would affect my opinion of whether the CSSOM questions here matter: what's the chance that at some point in the future we'll allow the nesting syntax inside the style
attribute? (This sort of syntax has been proposed and specified in the past, but it didn't get traction at the time.) I think the CSSOM is much more relevant for the style attribute than it is for style sheets, since it's more widely used there.
- No
@nest
, justCSSStyleDeclaration
in the CSSOM
- Criticism: That means
.cssRules
will also return non-rules? Would.insertRule()
also acceptCSSStyleDeclaration
?
I am not sure I understood the whole conversation between mdubet and emilio about this in #8738, but intermixing list of declarations (or CSSStyleDeclaration
s) and rules in specified order in an internal property (eg. contents
) seemed like an idiomatic solution.
The first list of declarations in contents
should be exposed by style
. .cssRules
and .insertRule()
should ignore lists of declarations in contents
. .deleteRule()
should eventually move declarations into the first list. It does not seem that painfull.
I did not understood why contents would need to be exposed by the public .cssRules
. I guess in order to add declarations after rules via the CSSOM. Assuming there is a need for this, a new interface like .insertDeclaration()
and/or styles
could be defined.
But accepting declarations in .insertRule()
, ugh. 😬
One question that would affect my opinion of whether the CSSOM questions here matter: what's the chance that at some point in the future we'll allow the nesting syntax inside the
style
attribute? (This sort of syntax has been proposed and specified in the past, but it didn't get traction at the time.) I think the CSSOM is much more relevant for the style attribute than it is for style sheets, since it's more widely used there.
My understanding is that this is basically planned, and it's just a matter of resource allocation. Inline style nesting is a huge use case.
Since it seems hard to get consensus on making this rule less author facing, could we explore going the other way? I.e. keeping is as author-facing, and trying to make it useful in some way.
Some ideas:
@sheet
rule that basically does named grouping. I wonder if these two could be combined. style()
conditionals on the current element? Or some other way to toggle these groups on or off or pick among two?Just brainstorming here. What would be some low hanging, easy-ish to implement functionality that could make this rule actually useful?
Perhaps we should simply name it @group
and allow an optional <dashed-ident>
before the {
, then we can figure out later what these can be used for.
I did not understood why contents would need to be exposed by the public .cssRules. I guess in order to add declarations after rules via the CSSOM. Assuming there is a need for this, a new interface like .insertDeclaration() and/or styles could be defined.
You really want authors to be able to see all the styles in the stylesheet, in the right order. If cssRules doesn't do that you need something else.
Another thing to note, not sure how well this interacts with @scope
... @andruud / @dshin-moz / @tabatkins / @mirisuzanne, do you know off-hand?
IIRC we either stash an implicit :scope
in the selector (but we can't with @nest
, because there's no selector), or use the &
which now includes the start bound, right? At least I recall a weird interaction between nesting and @scope
, but I don't recall the specifics...
That is, what does:
#a {
...
@scope (#b) {
color: red;
}
desugar to now? What would desugar to with @nest
and would it work correctly?
@emilio Nothing different for @scope
than the other nested group rules : we currently wrap the declarations in &
which desugar to :is(#b)
(because the <scope-start>
acts as a parent selector).
Exploring the use cases for a more general @group
rule is a good path forward (even just changing the name to anything less specific than @nest
is a progression imho)
Oh, so the fact that the scope rule is inside the #a selector is completely lost already? That's a bit weird / unintuitive but ok I guess
completely lost
Maybe it's not what you meant, but it's not completely lost, it's in the implicit &
in the <scope-start>
:
#a {
...
@scope (& #b) {
@nest {
color: red;
}
}
I think that will be just fine.
Perhaps we should simply name it @group and allow an optional
<dashed-ident>
before the {, then we can figure out later what these can be used for.
I support this too.
Although we could easily add a <dashed-ident>
to the prelude later, if/when we have a good reason for it. Maybe we just ship @group {}
for now, leaving the prelude wide open for whatever we come up with later.
Ah, cool, It's added to the start of the scope, that seems fine then I agree.
I don't mind calling it @group
(but if we do so then it probably should be a grouping rule after all, and not be restricted to declarations...)
I don't mind calling it
@group
(but if we do so then it probably should be a grouping rule after all, and not be restricted to declarations...)
Yes, agree.
So authors could (but likely won't) write :
:root {
@group {
--color-a: pink;
@media screen { --color-a: red; }
--color-b: lime;
}
}
Which would be equivalent to :
:root {
@group {
--color-a: pink;
@media screen { --color-a: red; }
@group { --color-b: lime; }
}
}
Although we could easily add a
<dashed-ident>
to the prelude later, if/when we have a good reason for it. Maybe we just ship@group {}
for now, leaving the prelude wide open for whatever we come up with later.
The thinking was to support this kind of usage.
It would be nice though if we could come up with some actual useful functionality for these groups other than simple grouping. Are any of the things I mentioned here feasible?
To be clear, this issue blocks actually carrying out #8738. The more we mess around with the details here, the higher the risk that no change will happen at all.
That was not the resolution at all. Please implement the
@nest
rule as currently specced by @tabatkins. It is far, far easier to change the details of that than to change the current behavior of nesting later. That's the whole point of the resolution we made on Wednesday!
With WebKit's position now clarified, and after some feedback on the Blink Intent-to-Ship, it's pretty clear that this issue is in fact blocking #8738.
This is a long-shot, not well thought-through - but is there a way for named-group declarations to be available as a form of name-spaced custom property. I don't think this would provide actual new functionality, but some syntax shortcuts for name-spacing and declaring a custom/standard property at the same time:
.now {
--base-padding: 1em;
padding: var(--base-padding);
--color-primary: teal;
--color-secondary: hotPink;
}
.groups {
@group --base {
padding: 1em;
}
@group --color {
--primary: teal;
--secondary: hotPink;
}
.access {
margin: var(--base.padding);
border-color: var(--color.--primary);
}
}
(Hope I'm not straying too far from the question at hand - but it seems like an otherwise-useful rule might help with WebKit's concerns?)
@mirisuzanne Love the creative thinking. Especially since making this rule useful seems like the only path forward with such strong objections from both sides.
Since @group {}
with no name needs to apply its content to the rule it's specified on would that apply its declarations when there is no name and store them for later reference if there is a name? That seems a little strange to me, and also clashes with #9992 and mixins.
Thinking about it some more, I actually think the IACVT idea might be the lowest hanging fruit and meshes well with the use case of representing declarations on the same level (i.e. it would not have to become a separate rule): we've always said IACVT is necessary because we've thrown away the fallback declarations by then, welllll… @group
allows us to preserve them! This also means it does not have to become a grouping rule. I think it's the idea that is closest to how @nest
was envisioned.
It could become a convention to have groups as levels of cutting edge-ness:
.foo {
/* Fallbacks for old browsers */
color: white;
@group --baseline-2024 {
color: color-mix(in oklch, var(--color) 30%, oklch(50% 0.01 none / none));
}
@group --cutting-edge {
color: oklch(from var(--color) 30% clamp(0.01, c * .8, .1) h / alpha);
}
}
The <dashed-ident>
can be dropped, people can always use comments for that.
Especially since making this rule useful seems like the only path forward with such strong objections from both sides.
But we also have to be careful with that. If it always has an effect then it can't be used to solve the problem at hand and we end up where we started.
This rule must be a noop in its simplest form.
I really like @mirisuzanne suggestion because it has no effect when there is no prelude. There is a lot of stuff to consider but also a lot of potential.
Some unstructured questions that spring to mind:
<dashed-ident>
's (and maybe <custom-ident>
)? ie. any regular declaration would remain untouched.<custom-ident>
's and <dashed-ident>
's? Also for things like @keyframes
? How badly does it break things like grid template area's?@mirisuzanne As outlined several times before, there is a simple and unambiguous approach to do namespacing of custom properties, but it does not extend well to accessing actual properties.
@group base {
padding: 1em;
}
@group color {
--primary: teal;
}
.access {
margin: var(base/*???*/padding);
border-color: var(-color-primary);
}
This rule must be a noop
Instead of @nest
, could we use another at-rule that is a noop?
@media all {}
This would not be new syntax.
This rule must be a noop
Instead of
@nest
, could we use another at-rule that is a noop?
@media all {}
This would not be new syntax.
Ooooooh I love this idea!! Is it really a complete no-op?
@media
? @media
?Another no-op rule:
@supports (all: initial) {
}
So then @media
/ @supports
would need a readonly attribute CSSStyleProperties style;
to accept declarations without any intermediate structure?
Yes, it would, and that was rejected early on as part of the Nesting design.
The @group
name, fwiw, seems perfectly fine to me. (And I don't have an opinion on whether it is a CSSRule (only has .style
) or a CSSGroupingRule (also has .cssRules
, tho the implicitly-created ones would never have anything there).) If the WG prefers that, I'm 100% okay with changing it.
Re: Miriam's idea of letting a name namespace the properties as custom props - I agree with Lea that it's a little strange it would work completely differently between the "no name" and "yes name" variants. I'd prefer to not have that sort of substantial divergence, and instead just do a separate at-rule for the namespacing functionality if that's useful.
Re: Lea's idea of having the grouped rules magically revert when they're IACVT - I agree with Romain that giving the rule a default behavior so it's not a no-op in the default case is probably a bad idea; it feels too magical that just nesting an MQ makes the styles in it automatically revertable, especially since the actual style rules in the MQ wouldn't have that. (But this could potentially be an opt-in behavior.)
This is the core issue here with any idea in this vein - the default behavior must be a no-op, because the parser makes these for plain ol' properties that were not intended to do anything special at all, and they need to act the same as the style rules next to them.
If we want to layer more behavior on this rule afterwards, to make it more worthwhile as an author-exposed construct, then cool! We can definitely do that, and since it has zero prelude it's very customizable for whatever purpose we want in the future. But the prelude-less version must be a no-op; it cannot be fancy.
But yeah, since WebKit has now come out strongly in the "disagree" camp, we need them to get into this discussion if they want any solution to happen - otherwise we'll either do nothing (retaining the reordering, also harming our ability to do mixins in the future) or do something over their objections. Compat is a ticking clock. @fantasai, @jensimmons, others?
@tabatkins What about @group tobikeshed {}
having the IACVT behavior I described (with tobikeshed
some suitable keyword) and @group {}
being a no-op? We could later expand it with more keywords too. Wrt implementation, could you estimate whether it's high or low effort?
Actually, we could just ship the no-op version first, and expand it with keywords later.
@fantasai Would a design like this be agreeable? a) if @group {}
ships before @group tobikeshed {}
b) if they ship together
If we define this as a CSSRule
, can we change to CSSGroupingRule
later?
@LeaVerou Yeah, that's what I meant by "(But this could potentially be an opt-in behavior.)"
Actually, we could just ship the no-op version first, and expand it with keywords later.
Exactly.
If we define this as a CSSRule, can we change to CSSGroupingRule later?
Yes, that's exactly what we did to CSSStyleRule, and that's vastly more used. This is especially true if we start off with the no-op version; once we make it more useful, it's more likely authors might expect to be able to nest rules in it, like they can with style rules, and then we run the risk of accidentally activating invalid code.
I think that's reasonable. E.g. in SVG <g>
by itself is also a no-op.
I actually think the IACVT idea might be the lowest hanging fruit
You didn't say "free", but I'd still like to clarify that we will not get this behavior for free just by having groups. @group
would need to behave like a mini-@layer
, and we'd basically need revert-group
. But it's certainly not impossible, and we can definitely explore it. I can investigate more if it becomes clear that we want to go down this path.
Yes, that's exactly what we did to CSSStyleRule, and that's vastly more used.
This is exactly what we didn't do in Chrome, because we have no way of proving that the change is safe. Also, this is the only part of CSS Nesting WPT coverage that was excluded from the Interop 2024 focus area, because we have no path towards actually making the change.
I still support "useful @group
" as a path forwards, but ideally we'd get CSSRule
vs CSSGroupingRule
right from the start.
This is exactly what we didn't do in Chrome
Wait, what? I was referring to adding .cssRules
to CSSStyleRule (via making it a subclass of CSSGroupingRule). Are you saying that Chrome isn't exposing the child rules of a style rule?
The spec initially exposed the child rules without making it a subclass of CSSGroupingRule, and then it was changed after Chrome shipped (https://github.com/w3c/csswg-drafts/issues/8940). Only Firefox actually implemented the resolution.
Ohhh, ok, so we do still have .cssRules on the rule, we just haven't shifted the inheritance structure yet. Okay, that's fine. By "what we did to CSSStyleRule" I meant "adding .cssRules", not necessarily "changing the inheritance tree". (My wording did imply that, tho.)
(tl;dr We support options 2 or 3 from the OP, with a slight preference for 2.)
WebKit strongly opposes introducing an @nest
rule for this purpose. We don't think expanding the syntax space of CSS for the convenience of CSSOM representation is an acceptable cost to authors, and prefer a solution that represents interleaved style declarations in the CSSOM in a way that does not have an externality on CSS syntax.
Our suggestion is to:
CSSNestedDeclarations
object inheriting from CSSRule
and having a .style
accessor, and use that to represent all the declaration lists in a CSSStyleRule
. It serializes as a raw declaration list..style
on the CSSStyleRule
directly (as currently) and as .style
on the first CSSNestedDeclarations
object inside .cssRules
, making .cssRules
a comprehensive and consistent representation of all the contents of the style rule, and .style
a convenient shorthand for accessing that first CSSStyleDeclarations
object..insertRule()
to parse declarations (or add .insertDeclarations()
), which appends those declarations to the immediately previous declarations list, if any, and otherwise creates a new CSSNestedDeclarations
object to append to and inserts that.We believe this approach has a lower cost to authors than introducing a new at-rule into the general syntax that has no true purpose other than CSSOM representation of interleaved styles.
We would also be OK with alternative solutions that don't introduce an at-rule, such as inserting CSSStyleDeclaration
objects directly into .cssRules
, or including only interleaved declaration lists in .cssRules
.
@fantasai's proposal looks acceptable to me, although including the "leading" declaration list in cssRules
is problematic, as it requires a new use-counter and waiting many months for good data.
It serializes as a raw declaration list.
That means it won't round-trip "structurally", like we talked about before. But maybe that's not so bad.
We would also be OK with alternative solutions that don't introduce an at-rule, such as [...] including only interleaved declaration lists in .cssRules.
In that case, I suggest that we resolve on the proposal, minus the change to cssRules
for now. "Making [cssRules] a comprehensive and consistent representation of all the contents of the style rule" does makes sense to me, but it would be nice if we could consider this separately in order to deal with the most urgent part ASAP.
Extend .insertRule() to parse declarations (or add .insertDeclarations())
It makes sense, but actually we could do this separately as well?
So the proposal is basically "@nest
, but without parsing or serializing the prelude ever", if I'm understanding it correctly?
I mean, I still think that parsing and serializing a rule is strictly better and more consistent, but if this is the only think WebKit is blocking on, then I guess I can live with it... We can always change course if people hit issues with .insertRule
and serialization and such.
For example, it's very weird that you'd call .insertRule
/ .insertDeclarations
, and that'd make stuff serialize to something like:
padding-top: 10px !important;
padding-top: 10px;
Which is something that otherwise would get simplified at parse time.
Serializing to something that explains what's going on like:
@nest { padding-top: 10px !important }
@nest { padding-top: 10px }
Seems a lot better to me. But I don't think it necessarily introduces any correctness issues other than that...
I'd still much rather WebKit reconsider their position of course but... ;)
@andruud
That means it won't round-trip "structurally", like we talked about before. But maybe that's not so bad.
It would, why wouldn't it?
including the "leading" declaration list in cssRules is problematic, as it requires a new use-counter and waiting many months for good data. ... I suggest that we resolve on the proposal, minus the change to cssRules for now. "Making [cssRules] a comprehensive and consistent representation of all the contents of the style rule" does makes sense to me, but it would be nice if we could consider this separately in order to deal with the most urgent part ASAP.
If we're unsure about Web-compat here, then I think it's fine to leave this point as an open question until we have more data.
It makes sense, but actually we could do this separately as well?
We could if we need to (and certainly we can take it as a separate resolution in the discussion), but it's part of making sure that the new CSSRule can get inserted/reordered/etc., so I think it's better to do this part as part of the package.
@emilio
it's very weird that [...]
I'm not sure I follow. The definition we're proposing for insertRules/Declarations
does not allow for consecutive CSSNestingDeclarations
objects in the .cssRules
lists.
Okay, so rephrasing the proposal a bit to make sure I understand it:
CSSNestedDeclarations
object, which is put into the .cssRules
of the parent at the appropriate place. This includes the initial decls.CSSNestedDeclarations
only has a .style
, and serializes as its declarations..insertDeclarations()
method on CSSGroupingRule
. This has some special magic to check if, either before/after the insertion point, there's already a CSSNestedDeclarations
object; if so, it'll append/prepend the decls into that object rather than creating a new one.
.insertDeclarations()
will fail, and passing a declarations to .insertRule()
will fail, since they each serialize as the wrong thing that won't parse right..deleteRule()
still works on CSSNestedDeclarations
.CSSStyleRule
's .style
accessor redirects to the .style
of the CSSNestedDeclarations
at index 0 of its .cssRules
..style
to CSSGroupingRule
, then, so @media
/etc get that too?)There are two issues I see:
CSSNestedDeclarations
, and .style
on the parent rule redirects to that, including when there aren't any declarations (you'll get an empty one). But this isn't backwards-compatible with @media
/etc, if their contents currently start with a nested rule (which they all do, today). We'd need to do more magic to only create the initial CSSNestedDeclarations
if there were initial declarations, and make .style
on the parent still work and auto-create+insert a CSSNestedDeclarations
if you set a property on it..deleteRule()
the initial CSSNestedDeclarations
? Presumably would need to fall into the same behavior as above, and magically re-create one as soon as you set a property.Most importantly, tho, my actual pushback wasn't on the details of the behavior, it was on the justification - what audience is affected by this, and how much benefit they are getting from it. This wasn't addressed at all in Elika's comment.
I continue to argue that (a) the audience for any of this is miniscule (just advanced authors, mainly tool authors, that actually crawl/manipulate the CSSOM structure in non-trivial ways), and the benefits are mixed at best, and don't actually solve any meaningful problems for authors.
Seriously, the pros:
The cons:
CSSRule
which can't be inserted into all CSSRuleList
s, only those of parent rules. (Or we get a new object that isn't a subclass of CSSRule
, and then .cssRules
now contains things that aren't CSSRule
s.).style
, where it might magically create a new rule when you interact with it.CSSRuleList
interface into being an ObservableArray (where you can just move the rules around), or have to add all the weird magic into it as well.If the above is accurate, then this approach is still unjustified. I strongly object to doing it unless/until @fantasai can show that there are reasonable benefits that outweigh the downsides, and are meaningful to a large enough audience. This has not yet been demonstrated.
(I mean, we decline to add things that are minor conveniences to a miniscule audience all the time, when the only cost is that it's a little bit of extra impl effort but not actually complex. See, for example, all the things we regularly reject from Values&Units. This proposal has real complexity costs associated with it, so it's clearly a CLOSED WONTFIX in my book unless there's something major I'm missing.)
(chair hat off)
I think an additional pro you did not list is avoiding exposing syntax that has no real purpose to ALL authors, which is one of the largest audiences we can affect.
Granted, that is a pro. Easy to teach around, but still, on the positives side.
I'm still objecting in general on the question of actual justification, but setting that aside for a moment, here's a much less complex variant that still mostly satisfies the request:
@nest
. Group rules just always use @nest
, unless we do decide we want to give them .style
as well for initial styles.)When serializing the children of a rule, an @nest
is serialized as its bare declarations if:
@nest
, and .style
, the @nest
isn't the first child rule. (If we don't add .style
to @media
/etc, then a first-child @nest
should serialize bare, but this is a decision we won't be able to go back on.)Otherwise, it serializes as normal for an at-rule.
This avoids all the complexities mentioned in Elika's proposal - no new parsing method, no magic behavior for that parsing method, no magic behavior for deletes, no magic behavior for setting a property in .style, no future constraints on our ability to evolve CSSRuleList
. As long as you're not modifying the OM directly, it always serializes back out without mentioning @nest
, so you won't notice the existence of the rule at all under normal circumstances. It just doesn't go to any effort to avoid serializing @nest
when you do use the OM to produce a funky structure.
I would accept this behavior under protest, if the WG ended up agreeing that this is indeed a use-case worth solving. (But I'm strongly objecting to Elika's current proposal.)
I don't think Elika suggested a new parsing method. She clearly said insertRule()
could be reused, but that we'd also accept insertDeclarations()
. In your rephrasing you just went with the latter?
I think whether the first declaration block is represented as a rule is up for discussion too. Perhaps there's less to settle if we don't do that.
And the reason we made this trade-off is because we are willing to accept a slightly less nice CSSOM (which is already quite terrible with all its string-based APIs and reportedly only used by 1% of web developers) to the benefit of all other web developers.
Opening this as requested by @astearns
In #8738 we resolved to stop hoisting interleaved declarations and introduce an
@nest
rule that means "exactly the same thing as the parent" instead of wrapping in:is()
, which is how interleaved declarations will be represented in the CSS OM. Since we were not able to get consensus on the specifics, but we had consensus that any solution along these lines is better than the status quo, we agreed that Tab would spec whatever (commit here), and we'd discuss the details later, since fixing the specifics is more web compatible than changing the current behavior after even longer.The issues around which we could not reach consensus were:
@nest
and it’s only introduced to represent interleaved declarations and rules, should they even be able to?@nest
rules are magically added around interleaved declarations should they also be removed during serialization?.cssText
)?@nest
rule? What if we simply use the existingCSSStyleDeclaration
object to represent interleaved rules? (proposed by @mdubet)setProperty()
work if we go with one of the designs that involve more magic?rule.style
?These are not orthogonal decisions: it seems clear that if
@nest
serializes to include an actual@nest {}
rule, that@nest
rule needs to also be valid author code. So essentially there are three possible designs:@nest
(proposed by @tabatkins, supported by @emilio @andruud @Loirooriol): The rule is automatically added around interleaved declarations, but there is no more magic besides that.@nest
(proposed by @LeaVerou, supported by @fantasai @astearns): The rule becomes a CSS OM detail, with no corresponding CSS syntax, and is removed on serialization (regardless of how serialization happens).@nest
, justCSSStyleDeclaration
in the CSSOM (proposed by @mdubet, supported by @LeaVerou @fantasai)..cssRules
will also return non-rules? Would.insertRule()
also acceptCSSStyleDeclaration
?For 2 and 3, there are also design variations based on the answer to 4 and 5 above.
My position:
@nest
or make it an internal detail. That said, there could conceivably be use cases for it. E.g. one of the problems with IACVT is that fallbacks are thrown away by the time the declaration becomes invalid. What if this was a way to preserve fallbacks?@nest
is functionally equivalent toCSSStyleDeclaration
and we should not be introducing new interfaces with philosophical purity being the only motivation (i.e. "but otherwise.cssRules
would be returning a non-rule?!?")setProperty()
on the base rule should just work, without them having to do tree walking to find the last nested rule. Like,setProperty()
is an incredibly common operation, and an API where calls tosetProperty()
have no effect are exactly the kind of dreadful APIs that make utility libraries proliferate.So I would propose a design that would minimize author exposure to all of this, and would just try to do what's reasonable when reading and modifying the CSS OM:
.cssRules
would containCSSStyleDeclaration
objects with interleaved declarationsinsertRule()
would acceptCSSStyleDeclaration
objectsCSSStyleDeclaration
objects would be merged.CSSStyleDeclaration
object cannot be at the start ofcssRules
. Inserting one should simply merge the declarations withrule.style
.Should
rule.style
be magic?One thing I'm ambivalent about is whether
rule.style
should be magic too. This would mean:rule.style
returns the union of all interleaved declarations (we can introduce another property to only get the non-interleaved ones)rule.style.setProperty()
(and accessors) adds to the last interleavedCSSStyleDeclaration
(if any are present). The third argument is turned into a dictionary with apriority
property and another property (name TBD) to reverse this behavior and actually add it to the first block of declarations.Pros & Cons:
If we decide to avoid magic here, we can make the API more palatable by:
rule
property that returns aCSSStyleDeclaration
for the union ofrule.style
and interleaved declarationsrule.setProperty()
method that would add the property at the end of the last interleaved declaration.rule.style.setProperty()
would continue to do what it currently does. Same forremoveProperty()
,getPropertyValue()
etc.