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-cascade-5] Allow authors to explicitly place unlayered styles in the cascade layer order #6323

Open
mirisuzanne opened this issue May 28, 2021 · 94 comments

Comments

@mirisuzanne
Copy link
Contributor

mirisuzanne commented May 28, 2021

Regarding CSS Cascade 5 (cascade layers), @jensimmons commented on another thread about layer ordering:

Would it be possible to allow Authors to set for themselves where in the cascade the "unlayered layer" resides? Maybe they even want to sandwich it in-between. A mechanism that's part of however they define the named layers & determine which layer is "first" & "second", etc.

By default unlayered style come first (lowest cascade priority) in the source order, but this would allow more explicit placement. Roughly (pseudo-code):

EDIT: That's no longer the case. In #6284 we reversed the behavior, and now unlayered styles have the highest priority. This explicit placement would still be useful, since there are use-cases for both approaches.

/* the default behavior */
@layer <unlayered-styles>, reset, framework, components, utilities;

/* placed "in-between" layers */
@layer reset, framework, <unlayered-styles>, components, utilities;

/* placed at the top/end of the layer order */
@layer reset, framework, components, utilities, <unlayered-styles>;

I think that feature makes a lot of sense, and I would likely use it as an author. A few considerations to keep in mind, as we develop a mechanism for this:

  • I would expect it to be repeatable for implicit sub-layers, as well the implicit outer layer
  • Since all layer names are currently custom-idents, we either need a reserved name, or some way of distinguishing the provided implicit-layer ident from author-provided idents.
@Alohci
Copy link
Contributor

Alohci commented May 31, 2021

Definitely welcome to have that level of control. If you didn't want a reserved name you could use different @ name. e.g.
@layer reset, framework;
@unlayered;
@layer components, utilities;

@fantasai
Copy link
Collaborator

fantasai commented Jun 1, 2021

Idk if it's a good idea, but one possibility would be to just leave out the identifier.

@layer reset, framework;
@layer;
@layer components, utilities;
@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Jul 5, 2021

Thinking about this a bit more: every layer (including but not limited to the default/root layer) has the potential for both direct style-rules and nested sub-layers. So this feature might be useful in nested context, not only in the root/default situation:

@layer one;
@layer;

@layer one {
  @layer two;
  @layer;
}

On the other hand, the root/default layer is the only place where authors might not be able to add explicit layering – for the sake of backwards compatibility. Once styles are layered, there is no harm in layering them further. So from that perspective, control is only needed for fully-unlayered styles.

Is it confusing if authors can specify different defaults inside each layer context? Is it more confusing if this only works at the top level, and does not work in nested contexts?

@Alohci
Copy link
Contributor

Alohci commented Jul 5, 2021

It would be a shame in my opinion if it didn't work the same in nested layers.

I'm also not sure how @import url(links.css) layer(mylayer); would work if @layer; didn't work the same when it becomes nested by the import as it does when the import doesn't inject it into a layer.

@mirisuzanne mirisuzanne moved this from To Consider to To Resolve in Cascade 5 (Layers) Jul 26, 2021
@astearns astearns added this to the EUR VF2F-2021-07-27 milestone Jul 26, 2021
@astearns astearns added this to Later agenda in EUR July 27 2021 vFTF Meeting Jul 26, 2021
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed Allow authors to explicitly place unlayered styles in the cascade layer order, and agreed to the following:

  • RESOLVED: Reserve the CSS wide-keywords (making the whole layer block invalid at parse time) for now and details TBD when we have better use cases
The full IRC log of that discussion <emilio> topic: Allow authors to explicitly place unlayered styles in the cascade layer order
<emilio> github: https://github.com//issues/6323
<emilio> miriam: this one is another coming from an earlier resolution
<emilio> ... we resolved that unlayered styles are lower pri
<jfkthame> present-
<emilio> ... jen asked about whether it'd be useful to tweak the unlayered styles priority
<emilio> ... there's some syntax proposals in the issue
<Rossen_> q?
<emilio> ... and I'd expect it to work at each level of layering
<emilio> ... are we happy with an empty layer rule syntax? Does this become too complex?
<emilio> florian: I could see use cases for top/bottom, has any non-theoretical use case come up for in the middle?
<emilio> miriam: yeah, you want components at the top and resets on the bottom, so you might want most of your styles between them
<emilio> TabAtkins: Like florian I see the use case but I'm not sure we need to solve it right now
<emilio> ... we could resolve the CSS wide keywords as layer names in case we want to solve them
<emilio> miriam: does that become a problem if additional wide-keywords are added?
<Rossen_> ack fantasai
<emilio> TabAtkins: theoretically? But we haven't added many over the years
<TabAtkins> s/resolve/reserve/
<emilio> fantasai: we could also do something that isn't a keyword
<emilio> ... I don't have strong opinion on having to solve this now, and I'd be ok reserving the wide-keywords
<fantasai> s/keyword/keyword, like an asterisk/
<emilio> florian: maybe I need to re-read the minutes for when we decided to switch top/bottom, I wasn't there and it seems !important could take care of jumping to the top
<emilio> miriam: main reason for that was that putting them at the bottom allows progressive enhancement
<emilio> ... sort of like when not all browsers had media queries you'd write the specific styles in there
<emilio> ... but lots of people think of layers as a way to hide their resets
<emilio> florian: I guess I see it more like the later but that also doesn't give me a strong use case for having unlayered styles in the middle
<emilio> ... I'd be fine reserving the wide keywords though
<emilio> fantasai: so there's the question of whether we add it now, if we don't we might want to just reserve the keywords
<emilio> miriam: if we're not sure if it's needed I'd be ok with reserving the keywords and delaying
<emilio> ... since it adds a fair amount of complexity
<emilio> florian: what do we need by reserving the keyword? Just making them syntactically invalid?
<emilio> fantasai: yeah, if you define @layer with that keyword the whole block is in invalid
<emilio> florian: is that progressively-enhanceable? If you add a layer that doesn't work and then it starts working...
<emilio> fantasai: why would you type it in if it doesn't work?
<emilio> florian: would it be wholly invalid or just ignored?
<emilio> TabAtkins: could we bring that detail back to the thread?
<emilio> Emilio: fwiw it seems simpler to make the whole block invalid at parse time
<emilio> RESOLVED: Reserve the CSS wide-keywords (making the whole layer block invalid at parse time) for now and details TBD when we have better use cases
@frivoal
Copy link
Collaborator

frivoal commented Jul 27, 2021

Given the resolution above, I think we all agree that @layer initial { } is simply invalid and the whole block is rejected. However, what of @layer foo, initial, bar;? Is that whole rule rejected too, or do we simply order foo and bar and ignore the non-existing initial layer?

I'm not sure, but I suspect making the whole rule invalid is safer. Otherwise, we might have people who introduce a @layer initial { } block, fail to notice that that doesn't do anything, order that layer into the middle of the stack with @layer foo, initial, bar;, and some day, if we do make the initial keyword apply in that situation, that changes the ordering of their whole page. I think this scenario would be less likely to happen if we ignore the whole rule, as then the author would be confronted with the fact that the ordering of their foo and bar layer don't work either, making it easier to notice.

@mirisuzanne
Copy link
Contributor Author

I'm happy with that approach, and drafted some spec language around it. Not sure if we need to get an official resolution, or not?

@mirisuzanne mirisuzanne moved this from To Resolve to To Review/Publish in Cascade 5 (Layers) Jul 27, 2021
mfreed7 pushed a commit to mfreed7/csswg-drafts that referenced this issue Aug 4, 2021
@mirisuzanne mirisuzanne moved this from To Review/Publish to To Consider in Cascade 5 (Layers) Aug 25, 2021
@mirisuzanne mirisuzanne moved this from To Consider to No change in Cascade 5 (Layers) Aug 25, 2021
@FremyCompany
Copy link
Contributor

FremyCompany commented Oct 6, 2021

I noted while thinking about this in the context of #6284 that explicitly pinning "initial" as a layer name isn't very convenient, because this has to happen once, and so that first declaration needs to be aware of all the layers that need to be above or below the initial layer (which might not be possible if you are using themes / unrelated add-ons).

Another approach I have been thinking about is to have two lists independently, all layers that must be above the unlayered styles, and all who need to be below.

A strawman would be as follow (`!important following an at-layer name means to put in the list after unlayered styles):

@layer reset { article h1 { margin: 0; } }
@layer theme { h1 { margin-top: 1.2em; } }
@layer special-overrides !important { h1:first-child { margin-top: 0 } }
.main-title { margin-top: 0.2em; } }

That would yield the following order for the layers : reset < theme < /initial/ < special-overrides.

Adding !important to individual values has an effect as usual, the !important on @layer only changes the order of layers in the list, but does not propagate to the values themselves (but the change in order for the layer will make the declaration be more important).

@FremyCompany
Copy link
Contributor

The advantage I see is that we are not prescribing anything here about the specifics of the default, authors can get one or the other depending on their needs.

@fantasai
Copy link
Collaborator

fantasai commented Oct 6, 2021

I think it's going to be useful and important to be able to have one-off layer declaration blocks that are above or below the default-layer styles, so we should have a syntax built into the @layer rule that says whether it goes above or below. Something like:

@layer [ up | down ]? <layer-name> { ... }

where

  • up increases the priority of normal rules (and decreases priority of !important rules)
  • down decreases the priority of normal rules (and increases priority of !important rules)
@mirisuzanne mirisuzanne moved this from No change to To Consider in Cascade 5 (Layers) Oct 6, 2021
@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Oct 7, 2021

I like the goal here, but have a few questions.

As I understand this, we would basically be creating two layer stacks — one above and one below the default — and then use the keywords to append layers to the top of either stack? What's the result of these cases?

@layer up one;
@layer down one;

Does that give us two layers with duplicate names (upper one & lower one)? Or do we only allow this keyword when the name is first used (in which case the second rule is invalid)? Another option is that we only provide this one-off syntax for truly one-off unnamed layers? In which case we likely need both the explicit placement, and the one-off option.

@layer  <layer-name> | [ up | down ] { ... }

I assume we don't want to allow moving layers around retroactively, so the second rule should not impact the layers defined in the first rule. That's what up/down imply to me. So if that's not what we mean, I think we should name the two layer stacks, and use their names as the keywords: something more like upper/lower or default/important or …?

@bramus
Copy link
Contributor

bramus commented Jul 3, 2024

What happens when a 3rd party stylesheet declares @layer *, reset, base, components, overrides;?

Would that cause all unlayered 1st party styles to have lower priority?

Yes. It can be part of author guidance to note that 3rd party libs shouldn’t really do that.

Also, when importing a 3rd party library, the author can load it directly into its own layer, so you’d end up with thirdparty.*

@knowler
Copy link

knowler commented Jul 3, 2024

@bramus said (#6323 (comment)):

What about if we used * to indicate the unlayered ones? It doesn’t conflict with any of the existing names (which should b idents) and it already has the meaning of “everything”.

E.g. @layer *, reset, base, components, overrides would the unlayered styles first, with the lowest priority.

If no * is defined in the stylesheets using a layer statement, it is appended to the layer order (i.e. the current existing behavior).

To clarify, this would only affect the ordering of layers within the current “super”-layer? So for example, if this was set at the top-level, it would only affect the ordering of unlayered styles in the top-most layer. Layers nested within top-level layers would still be ordered the default way with the unlayered styles last?

Example of ordering for @layer *, reset, base, components, overrides with nested layers for base:

  1. * Unlayered
  2. reset layer
  3. base layer
    1. base.variables layer
    2. base.typography layer
    3. base.* “unlayered”
  4. components layer
  5. overrides layer
@bramus
Copy link
Contributor

bramus commented Jul 3, 2024

To clarify, this would only affect the ordering of layers within the current “super”-layer?

Good remark. Hadn’t thought of that, but I think that‘s OK? Especially when you load that 3rd part lib in its own layer, it wouldn’t form a problem.

@mayank99
Copy link

mayank99 commented Jul 3, 2024

What about if we used * to indicate the unlayered ones?

Just a nit: it would be more clear to use something like @layer !unlayered over @layer *. The latter could be confused to mean "all layers".


What happens when a 3rd party stylesheet declares @layer *, reset, base, components, overrides;?

I think this is not a problem if the first-party stylesheet already declares the layer order including the unlayered (*) layer higher up. The one that comes first is used to decide the final layer order (same as today).

@mcareydsgn
Copy link

mcareydsgn commented Jul 3, 2024

Quite frankly I think resolution from Issue #6284 was the wrong path to take and has led this discussion to more complex solutions.

The CSS Working Group just discussed Reconsider placement of unlayered styles, for better progressive enhancement?, and agreed to the following:

  • RESOLVED: Prop: Reverse the prior resolution. Unlayered styles are highest priority

The full IRC log of that discussion

Giving unlayered styles the highest priority is not progressive enhancement. A developer setting layer order is intentional and should be given higher weight. Giving priority to unlayered styles makes it difficult to use CSS layers going forward especially if you are unable to control the unlayered order or there is a large amount of legacy CSS . I outlined this in my comment above.

Update 7/5/2026:
Given the recent responses in Issue #6284, I have a better understanding of why the decisions were made. I understand CSS frameworks using layers would make it impossible for sites to override if they were using unlayered styes only. My use case is probably not typical as my CSS uses layers but it needs to outweigh the site (unlayered) it is being used on. It is not a framework, it is a marketing and advertising landing page that needs to respect product brand styles over the site it lives on. I have no control over the CSS so layers is just not an option.

@knowler

This comment was marked as outdated.

@mirisuzanne
Copy link
Contributor Author

One AMP developer supporting our decision after the fact is not a sign that we made this choice for AMP - or even for framework authors generally. That was not at all the deciding factor, or even a main consideration. I have also never heard of that lib, but I'm glad the feature works for them. We also didn't make the decision because of author habits. It's very strange in a multi-mega-thread conversation to suggest that two off-hand comments were the only or main considerations. If you want me to explain why we actually chose this approach, I can (in the other thread). Either way, that ship has sailed.

Still, it would be useful for authors to have some additional controls. Over the course of this thread, we've seen a range of use-cases mentioned. But at this point we're almost completely re-treading the same ground over and over, with new people. There are three approaches that keep coming up (with minor variations of syntax). All of them have potential, and all of them have issues:

  1. !unlayered - (or initial or * or…) some name for the 'un-layered layer' so that it can be placed in the layer order like any other layer.
  2. !<layer-name> - (or up/down or the slash proposal or…) some way of specifying if a layer belongs above or below unlayered styles.
  3. !overrides - (or !important or !top or…) a pre-defined and permanently positioned layer that can't be moved, but can be added to.

In practice, I think @kizu's suggestion for option 3 here supports all the up/down use-cases, without any major new concepts or syntax – only one new layer name. And it avoids the issues with option 1. In some ways it also mirror's @mayank99's newer proposal - in that it establishes 'meta-layers' that our current layering can live in. But rather than thinking of it as two unnamed stacks (above and below) – or two new contexts - we just name the layer above, and everything else goes below.

At this point, that would be my strong favorite.

@mayank99
Copy link

mayank99 commented Jul 3, 2024

One benefit of !overrides over !unlayered is that it's fully opt-in, so there's no ambiguity around where @layer foo goes (it will always come before unlayered styles). I like it 👍

@kizu
Copy link
Member

kizu commented Jul 4, 2024

Just to put credit where it is due, the first mention of this was in @FremyCompany's comment, but yes, re-reading the discussion now, I still think something like !overrides is the better way to go about it.

And, I don't think there should be any implementation issues — it is just an ability to add one layer above the unlayered styles inside any other layer, including the topmost one.

Can we add this to the agenda? (cc @astearns) If we could resolve on this, and if the browsers will implement it, it could be a quick win. A late one, but nevertheless. (I still find myself wanting to use this feature all the time when writing custom overrides for various sites; and if we had it in browsers today, that use case would be covered immediately).

@youknowriad
Copy link

I've been watching this issue for some time, and I'm failing to see how the !overrides solution address our use case in WordPress.

Let me share some context. For a WordPress, We have a number of implicit layers today that are mostly definited using specificity.

  • Reset Layer
  • Theme Styles Layer
  • Block Styles Layer (provided by Core and Plugins)
  • User Styles Layer

Also plugin authors can inject random CSS where we don't really know which layer the layer.
We would like to explore CSS layers but there's a strong backward compatibility policy which prevents us from doing any breaking change.

We think that the unlayered styles that we basically don't control (so can't change them at all) should probably go between "theme" and "block styles" or potentially between "block styles" and "user layer" which means that there are potentially two to three layers that should precede the "unlayered" layer.

How do you think this could be handled.

@kizu
Copy link
Member

kizu commented Jul 4, 2024

@youknowriad I don't think any of the proposals will cover your use case? Given unlayered styles is a monolith, you can't have something that goes in between its parts, unless you wrap the whole preceding part with an @layer, but then it will be the backward compatibility you mention.

The !overrides won't solve all the problems, but I don't know if any other proposal here could help in your case better.

But regardless of which solution will be chosen, using any layers at all will lead to the same backward compatibility issues, right?

If you'd want to move forward, then one scenario could be: introduce a setting that will make WordPress wrap your current lower implicit layers with explicit ones.

Let's say you'd select the Reset and Theme to go below unlayered — you could start from separating those. When !overrides or anything similar is introduced, you'll use this option to wrap the later half — the Block and User layers with it, moving those after any unstyled CSS (although, as it sounds, maybe only the User styles should be there? Alongside maybe some core utilities that need to override anything, but not all the core/block layers).

If you can describe how a different proposal covers your use case better, please do!

@youknowriad
Copy link

Given unlayered styles is a monolith, you can't have something that goes in between its parts, unless you wrap the whole preceding part with an @layer, but then it will be the backward compatibility you mention.

I was not thinking about adding parts of the same layer in-between other layers, but more thinking that the following would produce the less disruption for us.

@layer reset, theme, blocks, !unlayered, user;

or alternatively

@layer reset, theme, !unlayered, blocks, user;

The !unlayered is still one and only layer, it's just that at this point, I'm not 100% sure which one would be better for us between the two options above.

But regardless of which solution will be chosen, using any layers at all will lead to the same backward compatibility issues, right?

yes, I believe so, the question I guess is which would result in the minimum breakage and to be honest only experimentation will help us figure it out. What's clear is that the unlayered layer having the smallest or the highest priority probably won't work for us.

If you'd want to move forward, then one scenario could be: introduce a setting that will make WordPress wrap your current lower implicit layers with explicit ones.
Let's say you'd select the Reset and Theme to go below unlayered — you could start from separating those. When !overrides or anything similar is introduced, you'll use this option to wrap the later half — the Block and User layers with it, moving those after any unstyled CSS (although, as it sounds, maybe only the User styles should be there? Alongside maybe some core utilities that need to override anything, but not all the core/block layers).
If you can describe how a different proposal covers your use case better, please do!

If I'm not you're suggestion that theme and reset use the same layer but that's not something we want, we do want these to be separate layers, one taking precedence over the other.

@kizu
Copy link
Member

kizu commented Jul 4, 2024

What's clear is that the unlayered layer having the smallest or the highest priority probably won't work for us.

The !overrides makes it so the unlayered styles will be in the middle, so it covers your case? All regular layers will have the smallest priority, the layers inside !overrides will have the highest, the unlayered styles will be in the middle.

There is no functional difference between

@layer reset, theme, !unlayered, blocks, user;

and

@layer reset, theme, !overrides.blocks, !overrides.user;

The only difference is what is explicit: the mention of the unlayered styles, or the mention of the styles that go on top of them.

(Which is actually a good thing to clarify in the specs: it should be possible to define the order of layers inside !overrides using an @layer statement, but with all overrides being always on top of the other mentioned layers, so the above is the same as @layer !overrides.blocks, reset, theme, !overrides.user really, which is probably not something most will write, but something that we could allow to do.)

@youknowriad
Copy link

youknowriad commented Jul 4, 2024

Thanks it was not clear to me that overrides can be applied to multiple layers, I thought it was just a hidden layer after the "unlayered" one (based on some comments above). Thanks for the clarifications.

@bramus
Copy link
Contributor

bramus commented Jul 4, 2024

There are three approaches that keep coming up (with minor variations of syntax). All of them have potential, and all of them have issues:

  1. !unlayered - (or initial or * or…) some name for the 'un-layered layer' so that it can be placed in the layer order like any other layer.
  2. !<layer-name> - (or up/down or the slash proposal or…) some way of specifying if a layer belongs above or below unlayered styles.
  3. !overrides - (or !important or !top or…) a pre-defined and permanently positioned layer that can't be moved, but can be added to.

A 4th option would be a new at-rule keyword to indicate that the added layers are “strong layers”. These strong layers stack on top of unlayered styles.

We could do something like @slayer but I’m quite sure a certain Trash Metal band won’t like that. A syntax I like – and which I mentioned it in the discussions about strong/weak scoping before is @layer!. It exclaims that the layers you are adding are strong layers and stack on top of each other after the unlayered styles.

@layer reset, base;
@layer! components, overrides;

The resulting stack – from low to high – becomes reset, base, (unlayered), components, overrides.

Advantage of this new at-rule keyword is that authors can feature detect it (@supports at-rule(@layer!)). It also prevents later rules from trying (and failing) to reposition the unlayered styles: as an author you don’t directly reposition the unlayered styles inside the layered ones, instead you put the unlayered ones before or after the unlayered ones.

@kizu
Copy link
Member

kizu commented Jul 4, 2024

@bramus Does this mean that we couldn't mention the strong layers in the “short” notation when adding styles to them?

@layer foo {
    @layer! bar {
    }
}

no longer could be shortened to something like @layer foo.!overrides.bar {}.

I also like the explicitness of the !overrides where you immediately understand where a layer mentioned inside will be. The @layer! is short, but maybe too short, I'd much prefer something like @layer-overrides, but I still think that having a single “system” layer is the most straightforward way.

And we still should be able to detect their support with @supports at-rule() using the descriptors feature detection I think, see #2463 (comment) (given browsers will implement it).

@rthrejheytjyrtj545
Copy link

Option 1 avoids unnecessary duplication by specifying @layer initial before any other layers. Option 3, on the other hand, forces you to either add !overrides to each layer name, or have each layer block indented by nesting in a @layer !overrides {}.

By the way, the exclamation mark at the beginning of the naming of each option looks like it would wash them away from the fact that there are reserved names in the specification for future compatibility, and thereby discourage from choosing option 1.

@bramus
Copy link
Contributor

bramus commented Jul 5, 2024

no longer could be shortened to something like @layer foo.!overrides.bar {}.

I was originally thinking that names shouldn’t be affected by that layer being strong or weak, so that layer would be shortened to foo.bar.

However, I can see now that it can get complicated. If you do @layer foo.bar to append, it’s not clear if one of those parts (or all parts) is strong or weak.

Tweaking that to become @layer foo.!bar then brings us back to effectively option 2 which Miriam listed.

So yeah, that @layer! thing I proposed doesn’t seem to cut it :-/

@mcareydsgn
Copy link

mcareydsgn commented Jul 5, 2024

Is there a reason why "unlayered" should not be a keyword to represent the placement of the unlayered styles in an @layer definition? Initial and * do not really make much sense to me as they already have a meaning in CSS. Initial being an initial value and I do not really think of unlayered styles as an initial value. When I see * it means it can be any or all things. Unlayered styles are something specific so the * does not really fit. I also don't really like the overrides options, it feels like adding important flags to a CSS rule.

This seems easy to read and understand. It goes from lowest to highest

@layer reset, theme, unlayered, blocks, user;

or

@layer reset, theme, blocks, user, unlayered;

and if you don't specify the unlayered in the definition it works as it currently works

@layer reset, theme, blocks, user;
@mirisuzanne
Copy link
Contributor Author

Is there a reason why "unlayered" should not be a keyword to represent the placement of the unlayered styles in an @layer definition?

@mcareydsgn there are two reasons we might not want to go that way:

  1. The 'unlayered' name hasn't been reserved, so authors could already be using it. How likely is that? Probably not likely, but we would have to do significant testing before we make that kind of breaking change.
  2. More important in my mind: it still has all the other issues associated with that approach (option 1 in my list).

Getting the right name is essential, but first we have to make sure we agree on how the feature should work. My goal for having this on the agenda would be to choose one of the three or four rough approaches – and then we could focus on the exact naming details from there.

@mayank99
Copy link

mayank99 commented Jul 5, 2024

I also like option 3 because it creates a symmetry between this and #10094. And it can be made even more intuitive with symmetric names like @layer !first vs @layer !last.

To refer to this whole feature without using any syntax from any of the options, I will use the term "post-unlayered styles", since the goal is to allow layered styles after unlayered styles. (Hopefully this helps instead of adding even more confusion).

Putting aside bikeshed concerns, I would like to get clarity on two points:

  1. Should we allow placing post-unlayered styles inside a layer?
    • Assuming option 3, this question can be rephrased as "Is foo.!overrides possible?"
    • I can see it being useful, but at the same time it feels like you should just use regular sub-layers if you're already using layers.
    • I think it would be simpler if this !overrides (or <insert-name-here>) syntax was only allowed at the top-level outside any other layers.
  2. How does revert-layer work with unlayered and post-unlayered styles? My understanding is listed below (assuming there is no !important or active animations anywhere):
    • revert-layer used in an inline style attribute will revert to the post-unlayered styles.
    • revert-layer used in post-unlayered styles will revert to unlayered styles (after reverting any nested layers inside post-unlayered styles). The assumption here is that unlayered styles are effectively treated like an implicit layer.
    • revert-layer used in unlayered styles will revert to layered styles, and then to shadow context, and then to presentational hints (same as today).
@mirisuzanne
Copy link
Contributor Author

  1. I believe any solution we pick has to work in a nested context. Because un-nested layers very easily become nested layers on-import. It wouldn't work for that to suddenly change the relationships between layers in the imported stylesheet.
  2. Your understanding matches mine.
@DarkWiiPlayer
Copy link

DarkWiiPlayer commented Jul 8, 2024

So, maybe I'm just missing something, but can't !overrides be used to simulate !my-layer-name anyway by simply nesting custom layers inside !overrides?

It does feel a little weird that no such extra nesting has to be done with layers below unlayered styles, but from a practical view, I don't see how these two proposals differ much if at all.

The only thing is, I don't remember if @layer !overrides.my-layer { /* styles */ } would currently be correct syntax (I know it's a thing for ordering layers at least)

Just one extra thought: I feel like !overrides and important! are close enough as keywords that it might be worth considering naming this magic layer important! as well, to make it more obvious what it does for people reading CSS that don't keep up with every latest addition.

That would get us to something like:

@layer important!.code-font {
   code { font-family: monospace }
}

which to me both looks nice enough and should cover most use-cases I can think of.


@mayank99 1. Should we allow placing post-unlayered styles inside a layer?

I can see cases where one CSS author might inject rules into another author's layer for customisation that should be overridden, so it makes a lot of sense to define every layer to implicitly have a nested !overrides layer internally.

@mayank99
Copy link

mayank99 commented Jul 8, 2024

@DarkWiiPlayer

can't !overrides be used to simulate !my-layer-name anyway by simply nesting custom layers inside !overrides?

Yes, this was covered above ("There is no functional difference…").

I feel like !overrides and important! are close enough as keywords that it might be worth considering naming this magic layer important! as well, to make it more obvious what it does for people reading CSS that don't keep up with every latest addition.

This was also covered above. !important does something very different and is a bad name for this.

I can see cases where one CSS author might inject rules into another author's layer for customisation that should be overridden, so it makes a lot of sense to define every layer to implicitly have a nested !overrides layer internally.

I don't see how !overrides helps here; it's no different from injecting rules directly into the layer/sub-layers, and it doesn't offer any protection because the "internal" !overrides can still be accessed from outside. At the top-level, !overrides solves a very real problem, while inside other layers, it's merely a convenience/consistency thing at best.

In any case, I'm not denying that it can be useful; I'm just concerned about the complexity.

See also Tab's comment in #10094 (comment) ("I don't think named layers need an initial sublayer.")

@kizu
Copy link
Member

kizu commented Jul 8, 2024

I was thinking regarding the “should every layer have its override (and possibly defaults) layer?”, and there are valid use cases for both having a way to place something before/after everything in some layer, and having a way to put something before or after globally.

So, we have several different cases:

  1. An ability to place something after the unlayered styles inside any layer, something like an !overrides layer.
  2. An ability to place something before the unlayered styles inside any layer. As Tab says, it is possible to set it up right now, but I can see use cases where you could want to do it without this setup, like when importing some external library, and wanting to insert the styles in a specific place.
  3. An ability to place something before everything or after everything regardless of the layer structure. I can see how this could be useful, but I think this is a separate issue (or two), and I am not entirely sure if this should be a part of layers specifically, or something else. If we were to make it a part of layers, I'd propose something like @layer-root which will basically be a way to access layers absolutely. The @layer right now is relative, and append-only. With something like @layer-root !overrides you could set the styles to the overrides of the root layer, for example, regardless of how your code is nested. This could also allow us to escape any layer and add styles to any layer if we know the “path” to it from the root one. But any of these comes with the breaking of the contract that layers are “isolated” right now.

But I would suggest us to focus on the 1 in this issue, and create separate ones for 2 and 3 (edit: there is already #10094 for 2, though with some parts of 3). I think all of these have valid separate use cases, but 2 is more of a “good to have”/“syntax sugar”, and 3 needs more thoughts (should it be @context like in #10094 (comment)? Should it be possible with @layer-root or something else? Do we even want something like this? Etc).

Outside the bikeshedding of the !overrides naming and questions over how exactly do it works, I think we can resolve on adding 1, then discuss what should be the preferred syntax for it separately, but also taking into consideration the potential syntaxes for 2 and 3 for things to be consistent.

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