w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.52k stars 674 forks source link

[css-cascade] Custom Cascade Layers (formerly "custom origins") #4470

Closed mirisuzanne closed 3 years ago

mirisuzanne commented 5 years ago

This relates to the Cascade Specification, along with a number of "specificity" concerns and proposals (such as #2272 & #3890 & the :where() selector).

Much of my work with design systems has revolved around helping companies define layers of abstraction: building tokens, then defaults, then patterns, components etc. That's a common approach, whether we call it OOCSS or Atomic Design or ITCSS or something else. In order to do that, we often have to be very careful with matching specificity to layer – so components override patterns, and so on – and third-party tools can easily break a delicate balance.

It strikes me that cascading origins & !important are designed to solve that same problem on a larger scale (UA, user, author), and then reverse-order for !important styles. It's a pretty clever solution, but !important is a blunt instrument for handling layers inside the author origin.

I doubt most developers think about cascading origins, or the role importance plays in it - and at this point they don't really need to for practical reasons. I don't have a full solution here, but a rough sense that providing control of custom cascade origins (within/around the author origin) might help:

A few notes on finding a syntax/approach that would work:

I realize this gets difficult to define quickly:

I hope that all makes some sense. I'd be curious for additional thoughts on this, and happy to clarify anywhere I can.

mirisuzanne commented 5 years ago

It strikes me that in some ways specificity already works in isolated layers:

Clearly that was the original intent: components (IDs) override patterns (classes/attributes) override defaults (elements). But it maybe falls apart in modern use for a few reasons:

That could lead us down a path of providing more specificity layers rather than origin layers? I'm not sure…

LeaVerou commented 4 years ago

The selector-specificity :where() approach is both narrowly-applied and removes all specificity, which could still be a useful tool within origins

Its goal is exactly the opposite: because it's a pseudo-class, it allows only removing part of the selector from the specificity calculation, enabling authors to distinguish importance-signifying criteria from merely filtering criteria. Of course it can be used for the whole selector, but it doesn't have to be, nor was that the primary use case I had in mind when I proposed it.

css-meeting-bot commented 4 years ago

The CSS Working Group just discussed Custom Origins.

The full IRC log of that discussion <fantasai> Topic: Custom Origins
<jensimmons> https://noti.st/jensimmons/QOEOYT/three-topics#srFUYHC
<astearns> github: https://github.com/w3c/csswg-drafts/issues/4470
<TabAtkins> jensimmons: Possibility of custom cascade origins, controlled by authors.
<TabAtkins> jensimmons: Part of a larger convo, which could be called "modernize the cascade"
<TabAtkins> jensimmons: Why modernize? Some folks argue that specificity was designed for a simpler time, when one or a small number of people wrote the CSS for a site. Today CSS is maintained over years by large teams, and the cascade gets really hard.
<TabAtkins> jensimmons: If a dev gets a ticket, they can't really rearchitect the whole page's cascade to fix that one thing.
<TabAtkins> jensimmons: Lots of ways people attack this.
<TabAtkins> jensimmons: (1) just do it right the first time
<TabAtkins> jensimmons: (2) OOCSS, SMACSS, BEM, etc
<TabAtkins> jensimmons: (3) Only ever use one class, to give identical specificity and remove the cascade.
<TabAtkins> jensimmons: (4) overuse !important
<TabAtkins> jensimmons: (5) CSS-in-JS, ignoring cascade again
<TabAtkins> jensimmons: Problem there is no way to control order CSS is loaded. No wonder the cascade is confusing!
<TabAtkins> jensimmons: (6) just inline-style everything, screw selectors
<TabAtkins> jensimmons: Why not use specificiy as designed?
<TabAtkins> jensimmons: IDs increase specificity, but can only use it once per page
<TabAtkins> jensimmons: Not great for components.
<TabAtkins> jensimmons: Element selectors work well for simple defaults, but too dependent on doc structure, and hard to use otherwise.
<TabAtkins> jensimmons: So leaves a lot of these teams only using classes, attributes, and !important
<TabAtkins> jensimmons: [shows example]
<TabAtkins> jensimmons: [Tailwind CSS]
<TabAtkins> jensimmons: [everything's inline, with no cascade]
<TabAtkins> jensimmons: A lot of possible ideas here too, web components, scoping, etc.
<TabAtkins> jensimmons: A project I did last year is how to protect CSS from this hate.
<TabAtkins> jensimmons: So we put together a hard-core course on teaching the cascade.
<TabAtkins> jensimmons: Miri Suzanne did a deep dive into the history/etc.
<TabAtkins> jensimmons: She began thinking about how we could change CSS to modernize the cascade and work better.
<TabAtkins> jensimmons: One of her ideas is to extend selectors, in #4690.
<astearns> https://github.com/w3c/csswg-drafts/issues/4690
<TabAtkins> jensimmons: Another idea is to allow authors to make custom cascade origins.
<TabAtkins> jensimmons: I didn't really know what a cascade origin was until Miri taught me.
<TabAtkins> jensimmons: [describes the cascade origins]
<fantasai> See https://www.w3.org/TR/css-cascade-4/#cascade-origin
<TabAtkins> jensimmons: Proposal is for custom origins. Say, create 3 named origins (get !important variants automatically that work as expected), and put styles in the chosen origin to get auto-overriding.
<TabAtkins> jensimmons: So use case.
<TabAtkins> jensimmons: Reset styles in one origin, design system in another, then one-off overrides into a third.
<TabAtkins> jensimmons: Or split apart the design system: reset -> defaults -> patterns > layouts -> components, all distinct origins.
<emilio> q+
<TabAtkins> jensimmons: Or CMS Core -> CMS Extensions -> Base theme -> site styles
<TabAtkins> jensimmons: Or a team trying to rewrite their CSS. Can't fix it all at once, but could put all their old code in one origin, and put their new code in a higher origin, to piecemeal fix it as they go.
<TabAtkins> jensimmons: Or Bootstrap -> 3rd party -> ad networks -> actual site styles
<TabAtkins> jensimmons: Adventages?
<TabAtkins> jensimmons: Coudl help with specificity wars between frameworks and author styles.
<TabAtkins> jensimmons: Could put !important back into its proper role, rather than being abused just to get a second origin.
<TabAtkins> jensimmons: Or just using origins as a type of !important; might be just as annoying?
<TabAtkins> jensimmons: Pulled some use-cases from Twitter (already mentioned)
<TabAtkins> jensimmons: So what do you think? Want to pursue?
<hober> q?
<florian> q+
<fremy> q+
<astearns> ack emilio
<fremy> q-
<TabAtkins> emilio: I'm a bit confused abuot !important.
<TabAtkins> emilio: If you want ad networks on an origin, and your styles on a higher origin, the ad networks could still override everything with !important style. Maybe that's not what you want?
<bkardell_> q+
<TabAtkins> emilio: Second, it may be invalid, but IDs *can* be repeated on the page...
<TabAtkins> emilio: There are ways for authors to use cascading origins that have better behavior - web components.
<fantasai> fantasai: They're really hard to use
<fantasai> TabAtkins: And also won't handle these use cases
<TabAtkins> TabAtkins: WC doesn't solve any 0f Jen's use-cases, tho.
<TabAtkins> emilio: When we discussed custom element default styles behavior, Apple was strongly against. Unsure if the'd still have complaints.
<fantasai> i/emilio/iank_: We should add declarative shadow roots
<TabAtkins> hober: I'l talk to Ryosuke/Antii, see if they have feelings on this.
<emilio> Though ++ to declarative shadow roots
<astearns> ack florian
<TabAtkins> florian: I think it's a brilliant idea.
<TabAtkins> florian: We've had the luxury of multiple origins here in CSS, letting us design thru problems. Authors haven't had that.
<TabAtkins> florian: I think it would be great. Almost want to stop talking about whether or not to do it and jus tstart talking syntax.
<TabAtkins> florian: Even as a singl eauthoe this seems useful.
<TabAtkins> q+
<astearns> ack fantasai
<TabAtkins> fantasai: I always want to say I love it.
<astearns> ack dbaron
<TabAtkins> dbaron: I'm also a big fan.
<TabAtkins> dbaron: There are multiple choices we coudl make about !important.
<TabAtkins> dbaron: Don't have to say they go in the opposite order. They could go in the same order, or be configurable, etc.
<fantasai> +1
<TabAtkins> dbaron: Maybe have the !important right after the normal origin.
<fantasai> essentially an origin can encapsulate its !important level
<TabAtkins> dbaron: So lots of options we could choose between, or let authors configure.
<AmeliaBR> +1 to dbaron says. Definitely don't want !important to automatically do reverse order.
<TabAtkins> fantasai: Along those lines, might have an origin with sub-origins.
<heycam> q+
<TabAtkins> fantasai: Which might have its !important held within the larger origin
<astearns> ack bkardell_
<TabAtkins> bkardell_: First, thanks for bringing it up.
<astearns> q+
<TabAtkins> bkardell_: I've had these same conversations and I think this is really healthy.
<fantasai> Examples (from slide 25):
<fantasai> Reset < Design System < Overrides
<fantasai> Reset < Defaults < Patterns < Layouts < Components
<fantasai> CMS Core < CMS Extend < Base Theme < Site Styles
<fantasai> Old Styles < New Styles
<astearns> q-
<fantasai> Bootstrap < 3rd-party libs < Ad network < Site Styles
<TabAtkins> bkardell_: To discuss what people are actually doing, rather than just relying on education
<TabAtkins> bkardell_: I think CSS-in-JS does have an order...
<TabAtkins> jensimmons: They can, but don't always
<TabAtkins> bkardell_: wrt WC, they don't solve all problems, but they do solve some. They're already .2% of the web archive, despite only getting the last impl this week.
<TabAtkins> bkardell_: Do we really rely on origin for UA level? I thought we kept them low speicficity.
<TabAtkins> TabAtkins: We don't use IDs, no, but we do freey use attribute selectors, which can easily clash if it wasn't for the origin difference.
<jensimmons> slides (again): https://noti.st/jensimmons/QOEOYT/three-topics#srFUYHC
<fantasai> yes, we really rely on origin for UA level
<TabAtkins> bkardell_: I do believe we'r emissing something here. I don't know if this addresses or exacerbates the problem. At some level it addresses their complaints, but by doubling down on what they're complaining about.
<TabAtkins> jensimmons: Agree.
<astearns> ack TabAtkins
<fantasai> Scribnick: fantasai
<fantasai> TabAtkins: I totally like this idea
<fantasai> TabAtkins: had similar thoughts, but never did the use case exploration
<fantasai> TabAtkins: Definitely agree we should pursue this, and the use cases made me absolutely sure we should pursue this
<TabAtkins> ScribeNick: TabAtkins
<astearns> ack heycam
<TabAtkins> heycam: I think it's very important fo rus to try to address these problems.
<TabAtkins> heycam: A little of a shame that it's taken several years after people started complaining, biut glad we're addressing it now.
<TabAtkins> heycam: What I like about this is that it's so simple, and slots into the existing model.
<astearns> q+
<TabAtkins> heycam: Not super sure about whether it really will capture all of these use-cases, or might need more discussion with real proponents of CSS-in-JS to see how well it works.
<emilio> q+
<TabAtkins> heycam: I'd want to be more sure this is the right way to go for solving that.
<TabAtkins> heycam: But see th eother use-cases anyway.
<TabAtkins> astearns: I agree this is very nice it slots into our model, but a little concerned it's not the general author model.
<TabAtkins> astearns: You had to learn about the concept anyway.
<TabAtkins> astearns: So as Tess said, "origin" is an overloaded term, maybe we can come up with something else?
<TabAtkins> [various suggestions]
<astearns> ack astearns
<astearns> ack fantasai
<dbaron> "style sources"?
<TabAtkins> fantasai: Some discussion about this addressing all the cases; I don't think it does, biut it addresses quite a few, and addresses the organizational layer of many projects.
<TabAtkins> fantasai: So I think it fits well with how people put together their sites.
<TabAtkins> fantasai: There's other places in the cascade where specificity gets unwieldy. I don't think WC is great here; it adds a *ton* of encapsulation.
<TabAtkins> fantasai: Another proposal was scoped styles in CSS, which might also help.
<jensimmons> q?
<bkardell_> if we had this, would we need leaverou 's zero specificity pseudo still too?
<TabAtkins> fantasai: They let you say "within this sidebar, these styles win over other things".
<fantasai> s/things/things, regardless of specificity/
<TabAtkins> TabAtkins: I think declarative shadow dom addresses a lot of the problems with WC; I'd like to explore that more seriously first before we add something that is 98% identical to WC's model, but with 2% weird differences that make impl complicated.
<astearns> ack emilio
<fantasai> bkardell, you wouldn't need it for an entirely swath of styles, but would likely still be useful locally, for specific selectors or parts of selectors
<AmeliaBR> q+
<TabAtkins> emilio: I agree this is neat. Is there a concrete proposal? Is that at the stylesheet level, or allow 3rd party styles to choose their origin, etc?
<TabAtkins> emilio: Depending on details it might solve some use-cases but not vice-versa.
<TabAtkins> emilio: Also need to figure out how this interacts with shadow dom.
<leaverou> bkardell_: I believe so. This is great, but sometimes you need more fine-grained control. E.g. when theming *within* the same origin
<TabAtkins> emilio: Shadow DOM introduces a stack of origins; introducing this naively makes it a matrix, which is harder.
<jensimmons> q?
<astearns> ack AmeliaBR
<TabAtkins> AmeliaBR: echo Emilio's concern that we need details to see exactly how this sort of thing works.
<TabAtkins> AmeliaBR: Coming up with specific proposals and cross-reffing them with specific use-cases would be helpful.
<TabAtkins> AmeliaBR: So we should work from the use-cases to what features we actually need.
<astearns> ack fantasai
<TabAtkins> fantasai: For "how do you control", an easy way to think of it would be the person importing the sytlesheet bea ble to say what level it imports at.
<TabAtkins> fantasai: And within each level, maybe you can have sub-ordering.
<florian> q+
<TabAtkins> fantasai: And with a nesting block that lets you specify the layer within a single file.
<TabAtkins> fantasai: Using numbers to establish the ordering might work if there's only one controller; multiple controllers gives you the z-index wars.
<faceless2> q+
<TabAtkins> emilio: My concern with nubmers or letting stylesheets choose their own levels becomes a z-index fight.
<astearns> ack florian
<TabAtkins> florian: One thing I'm a little concerned is how we figure out the syntax to have a migration path toward this from legacy CSS.s
<TabAtkins> florian: In particular, a syntax ignorable by old browsers is bad because the cascade will be all mushed up; but making it hide rules from old browsers means they'll just miss a lot of code.
<TabAtkins> florian: Writing everything twice is bad, but not having an upgrade path is bad.
<astearns> ack faceless2
<astearns> ack faceless
<TabAtkins> faceless2: What if you had two toolkits, importing the same stylesheet at different levels?
<astearns> zakim, close queue
<Zakim> ok, astearns, the speaker queue is closed
<fantasai> TabAtkins: Same as importing a style sheet twice, it's just present in both places
<fantasai> TabAtkins: cascades together; effectively later one wins
<TabAtkins> jensimmons: So got a lot of good issues and concerns.
<bkardell_> +1 to talk about "this set of problems" for sure
<TabAtkins> jensimmons: I do think it's worth looking deeply at the solutions we might need for the complete set of problems, not just what this particular solution could address, so we can tell if this is a good idea in the totality of a complete solution.
<TabAtkins> jensimmons: I've even convinced myself that if we ship this today by itself, it could get abused pretty badly.
<TabAtkins> jensimmons: (similar to people abusing Flexbox to do grids)
<TabAtkins> jensimmons: Putting this on Twitter, I got a lot of trepidation from folks. Powerful tool, could be bad.
<TabAtkins> jensimmons: But I got that people who really knew CSS the most thought this was a terrific idea.
<TabAtkins> jensimmons: I think it does require some teaching, but it's not that complicated.
<TabAtkins> jensimmons: So I'm hearing a tentative "yeah, this is good", but I think there is a bigger metaproject to modernize the cascade.
<TabAtkins> jensimmons: Also, Miri has been very active in Sass to push CSS to be a featureful language; did crazy things with Sass variables back in the day.
<TabAtkins> jensimmons: So I'd like to invite her as an IE.
<TabAtkins> [intentionally not minuting]
<TabAtkins> fantasai: Where to put it?
<TabAtkins> TabAtkins: Suggest putting it in WICG until it gels, then merge it into Cascade 5.
<fantasai> i/fantasai/astearns: So sounds like interest in the room, try to move proposal forward/
<TabAtkins> jensimmons: And I want to get some highly-skilled authors involved in the convo too, so hopefully WICG works there.
WebMechanic commented 4 years ago

came here via one of Jen's Tweet. https://twitter.com/jensimmons/status/1219351448028356609

* IDs are explicitly one-off, so we can't re-use anything in the component layer

well, actually CSS doesn't bother. You can have multiple elements with the same ID and they'd be styled according to the current rules. It's JS that'd likely choke or sth. like a XSLT processor.

Maybe I got lost in translation reading this proposal (and what happend around Jen's tweet) and I'm going to state the obvious; but here are my 2ct.

FWIW both Cascade and Specificity are and always have been perfectly fine the way they are: the closer to the element (origin/location/cascase) considering the selector specificity. Done. What's not to grasp? :) It's kinda like with using Layers and Masks in Photoshop: some ppl just can't get their head around it :) If you one doesn't understand pointers in C++ or references iin Java[Script] s/he equally screwed.

However, the culprits IMHO happen to be "modern" design patterns and philosophies like BEM or Atomic Design & such that are causing the issues ppl. apparently have with "specificity": you either have a rather flat hierachiy where everything has virtually the same specificity (i.e. BEM) (and origin/order matters) or the HTML is poluted with a gazillion cryptic utility classes and what-not that might end up raising the specificity over the roof. And then there are some Framework / Library creators that don't get it either and butcher our beautiful Cascade adding !important all over the place.

There's only one addition to the Level in the Origin line of things I'd imagine to be helpful in some situations and to some people. It'd be considerably 1. logical and 2. following the current rules of the Cascade but applied a different "weight" to stylesheets served from 3rd party domains (CDN et al) and one's own (sub-)domain. That's something crafty people can easily understand and hopefully handle responsibly. That's also how CDN based CSS Frameworks would get a lower significance than author and user styles. Currently only source order matters, IIRC.

With such a distinction equal selectors and equally specific selectors in an author style (even if @imported) served from the same domain/origin as the document could overrule one from a CDN'ed Framework w/o using !important.

Here's how things could work bold = "new" origin level (out of my head from lower to higher priority)

Since this additional level of origin would change the current browser behaviour, authors should "opt-in" to enable the new origin level by adding a <meta> tag to the document or sending a response Header. That's similar to the well accepted <meta viewport> which also changes the way CSS works with dimensions, or how oldIE was told to toggle its X-UA-Compatible mode back in the days.

René

keithjgrant commented 4 years ago

I am very excited by this proposal, and have definitely wished for this exact thing. Even if authors can't define n origins, just two would go a long way: One for "base" styles, and another for "module" styles.

I think in most modern development, there are fundamental differences between these two types of styling. Ideally, I would typically want base styles to apply to the whole page, even piercing into Shadow DOM; but they would be of a lower priority origin, so module styles would always override them. I'd even be interested in a way to do something like all: unset that reverts my module styles but leaves my base styles applied. Related comment: https://github.com/w3c/csswg-drafts/issues/3547#issuecomment-458232087

keithjgrant commented 4 years ago

applied a different "weight" to stylesheets served from 3rd party domains (CDN et al) and one's own (sub-)domain.

I think this might be useful to some, but it only addresses one specific problem that's a subset of this problem as a whole. Not everyone pulls in (say) Bootstrap from a CDN and then sticks some overrides on it. How would this help me override my own base styles? For me, I need more control over my styles and how they are applied.

tabatkins commented 4 years ago

Having N author-defined origins is realistically no more difficult than having two (in other words, the spec text and probably implementation for both would be nearly identical, just with one being locked into 2), so that's nice at least.

WebMechanic commented 4 years ago

Not everyone pulls in (say) Bootstrap from a CDN and then sticks some overrides on it.

by CDN I simply mean "not the same hostname".

I often set up one or more subdomains on the very same server pointing to the same files to overcome the connection limits (esp. for HTTP/1.1). So cdn.myserver.tld or img.myserver.tld and myserver.tld are actually the same machine and share the same root. Some trickery in Apache's .htaccess makes sure that only certain file types are delivered by and only accessible via the CDN URLs.

There you have you own "CDN" to stick some overrides on it :) Same simple rules apply.

dvoytenko commented 4 years ago

I think this proposal is very much needed from the point of view of components builders. But I'd also like to stress that a shared component would often involve two types of styles:

  1. Structural styles (e.g. display: flex for some horizontal layout). Maybe not quite !important, but the desire is to make it very hard to change this style from author/user stylesheets.
  2. Stylistic styles that a component provides defaults for, but expects to be overriden. E.g. :focus {outline: ...}, etc.

Someone above mentioned similarity with z-index and that seems to fit pretty well conceptually.

Another point I wanted to make is that ideally whatever spec ends up here, it'd be polyfillable with the today's technology. Perhaps a WebPack plugin could be provided that would order style/link tags or selectors to respect the new specificity ordering. This could limit the spec quite a bit, but backward compatibility is critical for this, imho.

mirisuzanne commented 4 years ago

I'm working to break out some more specific topics for consideration, but I think this could be a prime candidate for more face-to-face discussion.

mirisuzanne commented 4 years ago

I made a codepen demo that demonstrates how I currently approximate both "origins" and "scope" using custom properties:

In that way, each origin (variable) in the stack allows for scope (inheritance) internally, but higher origins will always take precedence.

mirisuzanne commented 4 years ago

We've written up a draft syntax proposal that addresses most of the open issues here:

https://gist.github.com/mirisuzanne/4224caca74a0d4be33a2b565df34b9e7

Marking for review with the full group…

Wolfr commented 4 years ago

Here's my feedback to this proposal.

Concerns

Positive: it might be easier to get started writing CSS (in a safer way)

On the more positive side, this could give newcomers a clearer placer where to “put their stuff”.

Currently libraries like Bootstrap in their SCSS variant already have ordered imports that more or less follow a logical specificity graph (from base to class level to utilities).

You can already choose to simply extend that system by adding a component between those imports. But it does take some expertise to know exactly how to split things up.

Over the years I've employed numerous strategies to work with the cascading aspect of CSS in a good way, in both the context of a non-namespaced framework (Bootstrap) or in specific contexts that require high degrees of CSS knowledge to execute well (BEM/ITCSS).

I find that newcomers have a hard time looking "through the framework" knowing where they can divide it into pieces and start extending it.

Let's say you have a Bootstrap-like system that is established that is set up like this:

@layer reset, objects, components, utilities;

Then the newcomer could be instructed to write their component in the components layer:

@layer components {
  .my-component {
    /* some style rule */
  }
}

When the CSS gets bigger, organising could be done as such:

@layer components {
   @import url(button.css); 
   @import url(panel.css); 
   @import url(tabs.css); 
}

(or rather with a preprocessor so that we don't have too much HTTP requests)

Positive: clearer theming

With this proposal I can see some novel logic happening in the theming world. Imagine you have to add theming to the above code.

@layer reset, objects, components, theme, utilities
@layer components {
   @import url(button.css); 
   @import url(panel.css); 
   @import url(tabs.css); 
}

@layer theme {
   @import url(button-theme.css); 
   @import url(panel-theme.css); 
   @import url(tabs-theme.css); 
}

Your theme can now live on a clear layer where it will always override the components layer.

A potential risk: misuse as an organisational tool

I see a lot of people who don't really get the cascade. One risk of this is that it will simply be used an an organizational method while it does have specificity side effects, creating new problems.

Imagine this:

@layer reset, objects, framework-components, custom-components, utilities

In this example the custom components will have a higher specificity than the framework components. The project team is using the layers for organisation purposes:

@layer framework-components {
   @import url(button.css); 
   @import url(panel.css); 
   @import url(tabs.css); 
}

@layer custom-components {
   @import url(reverse-tabs.css); 
   @import url(logo.css); 
}

But there's actually no reason they should. They should have a similar level of specificity. It would probably be better to write:

@layer reset, objects, components, utilities
@layer components {
   @import url(framework/button.css); 
   @import url(framework/panel.css); 
   @import url(framework/tabs.css); 
   @import url(custom/reverse-tabs.css); 
   @import url(custom/logo.css); 
}

All in all... (conclusions)

I welcome the efforts to improve upon CSS. I really don't like the flat non-cascading solutions that people escape to nowadays (like Tailwind CSS). I think the cascade can be such a strong tool if used correctly. If we can put tools in people's hands to use it in smarter ways that would be cool. But I think getting people to use this might be a challenge in itself.

chrishtr commented 4 years ago

I think this is a well-written proposal. Thank you for such a clear exposition. I think it definitely helps us explore the space of how far we could go with layer semantics. I also left a few questions and thoughts in the comment thread on the gist.

My summary feedback:

I think the block syntax looks good.

I have some concerns about the external-sheet loading syntax, in particular: whether developers will find it too confusing or difficult to debug, implementation complexity, and the potential for race conditions (example).

I have a concern about how well this proposal satisfies the use case of importing common third-party styling libraries.

This proposal does seem to be backward-compatible with an incremental implementation and shipping approach, along the lines of what I suggested here, which is great. (We'd pre-define certain built-in layers, not allow defining new ones, and not support url import syntax.)

It's also good that it appears this approach is also polyfillable.

tabatkins commented 4 years ago

the potential for race conditions (example).

As stated over in a comment on the gist, "order" means standard stylesheet ordering, the same ordering used for the final specificity tiebreaker and many other features (such as "last-defined" for @keyframes with the same name). So there's no race conditions here. If an early stylesheet loads late, it'll just insert its defined layers into the appropriate spot in the ordering, not append to the end.

whether developers will find it too confusing or difficult to debug,

This example is arguing against being able to import a sheet into a layer. Is this because of the temporal race condition confusion? Is it resolved now that it's clear there aren't race conditions?

I have a concern about how well this proposal satisfies the use case of importing common third-party styling libraries.

I'm pretty sure this is also caused by confusion over the race condition thing, and thus isn't actually an issue. Is this right?

This proposal does seem to be backward-compatible with an incremental implementation and shipping approach, along the lines of what I suggested here, which is great. (We'd pre-define certain built-in layers, not allow defining new ones, and not support url import syntax.)

I don't think this is easily compatible with predefined layers. I mean, we could do it, but it would be weird. We'd have to define a specific position for the predefined layer, and either add some more complex syntax to let custom layers go above or below it, or just put all custom layers in a specific position relative to it. I don't want to do the first, and the second means, in practice, that people just shouldn't use the predefined layer at all once custom layers are supported (since it's unlikely that the predefined name, if it fits in their name system at all, is appropriate to be at the start/end of their other layers).

chrishtr commented 4 years ago

As stated over in a comment on the gist, "order" means standard stylesheet ordering, the same ordering used for the final specificity tiebreaker and many other features (such as "last-defined" for @Keyframes with the same name). So there's no race conditions here. If an early stylesheet loads late, it'll just insert its defined layers into the appropriate spot in the ordering, not append to the end.

I think you're referring to this comment. Agree that it clears my concern about race conditions. There is though the (lesser, but real) concern about flipping ordering during load causing style thrashing.

I have a concern about how well this proposal satisfies the use case of importing common third-party styling libraries.

I'm pretty sure this is also caused by confusion over the race condition thing, and thus isn't actually an issue. Is this right?

My concern was regarding this comment I made in the gist: about how to avoid the main site knowing about the third-party layer names (this comment). @lilles mentioned a related concern.

As @tabatkins mentioned to me offline, I think my main concern is alleviated because the main stylesheet can wrap any third-party imported stylesheet in an anonymous or named layer, and then make sure to declare that layer at the right place relative to other main stylesheet layers.

I don't think this is easily compatible with predefined layers. I mean, we could do it, but it would be weird. We'd have to define a specific position for the predefined layer, and either add some more complex syntax to let custom layers go above or below it, or just put all custom layers in a specific position relative to it. I don't want to do the first, and the second means, in practice, that people just shouldn't use the predefined layer at all once custom layers are supported (since it's unlikely that the predefined name, if it fits in their name system at all, is appropriate to be at the start/end of their other layers).

If we first ship one or more predefined layers, and then later ship custom-definable layers, then yes the ordering relative to the predefined layers would probably be fixed. However, this would just mean the site should at that point stop using the predefined layers and use custom layers only, if it conflicted with their desired order, and then the ordering of predefined layers relative to custom layers doesn't matter, because the site doesn't use them. If the site included a third-party stylesheet that utilized the predefined layers, then it could be imported with a wrapping anonymous or named layer placed at the appropriate ordering position; this situation seems the same as the one in my earlier comment above.

Therefore I do think it's backwards compatible. If predefined layers are specified and shipped first, this has the following advantages as I see it:

tabatkins commented 4 years ago

If we first ship one or more predefined layers, and then later ship custom-definable layers, then yes the ordering relative to the predefined layers would probably be fixed. However, this would just mean the site should at that point stop using the predefined layers and use custom layers only, if it conflicted with their desired order, and then the ordering of predefined layers relative to custom layers doesn't matter, because the site doesn't use them.

Right, so at that point we're defining something that we know will be obsoleted almost immediately, but will provide a footgun (in the form of a name that authors aren't allowed to use) forever. Do we believe authors are clamoring for a solution that can be well-solved by a single predefined layer so hard that we think it's worthwhile to throw away effort like that?

Avoids all of the potential developer gotchas referenced on the gist having to do with nested layers

I just reread the gist and couldn't find any gotchas listed about nested layers. Can you elaborate?

Reduced complexity of understanding the feature for developers

Yes, a single predefined layer is simpler than multiple layers. But we're gonna move to arbitrary named layers anyway; they can't avoid that complexity. And having both a predefined layer and arbitrary layers makes the entire feature slightly more complex than it would otherwise be, so in the end it's slightly worse, not neutral.

(maybe?) Encourages shared CSS design patterns involving layers among different libraries, thereby making it easier for sites to adopt these libraries without learning new layer names and best practices.

I think this one's a stretch, yeah. Even with only one layer, there's still at least two completely distinct and very reasonable ways to use them that would cause bad clashes between libraries: with the layer as reset/defaults and unlayered code as normal, or with the layer as "normal" and the unlayered code as spot-overrides ("better !important").

chrishtr commented 4 years ago

Right, so at that point we're defining something that we know will be obsoleted almost immediately, but will provide a footgun (in the form of a name that authors aren't allowed to use) forever. Do we believe authors are clamoring for a solution that can be well-solved by a single predefined layer so hard that we think it's worthwhile to throw away effort like that?

If the second part were definitely going to ship soon after, then I would agree with you. I am suggesting we ship the first part and see if the additional complexity is warranted.

I just reread the gist and couldn't find any gotchas listed about nested layers. Can you elaborate?

Specificity/layer depends on method of importing (here). Possible confusion about how order of layer ordering works across stylesheets. Multiple loading. Need to remember to put the layer on all APIs that reference a stylesheet.

Yes, a single predefined layer is simpler than multiple layers. But we're gonna move to arbitrary named layers anyway; they can't avoid that complexity. And having both a predefined layer and arbitrary layers makes the entire feature slightly more complex than it would otherwise be, so in the end it's slightly worse, not neutral.

I don't think it's clear to me that we will need arbitrary named layers. I do think there is likely a good argument for more than one predefined layer though. I wasn't saying it's either one layer or arbitrary layers, there is a midpoint.

I think this one's a stretch, yeah. Even with only one layer, there's still at least two completely distinct and very reasonable ways to use them that would cause bad clashes between libraries: with the layer as reset/defaults and unlayered code as normal, or with the layer as "normal" and the unlayered code as spot-overrides ("better !important").

I'm not sure what the difference is between these two interpretations, can you clarify?

chrishtr commented 4 years ago

AIUI the current gist proposes that defined layers have higher specificity than un-layered style rules. This would mean that if a third-party library wanted to use a layer that was earlier in the cascade than an existing site (e.g. to provide a base layer for its components that are meant to be overridable by the site), then it's not possible to do this without changes to the site's style sheet to add a layer for it.

This will make it harder to deploy updated third-party libraries without waiting for sites to adopt custom layers first.

mirisuzanne commented 4 years ago

I don't think it's clear to me that we will need arbitrary named layers.

To me, this is fundamental. The ability for authors to name & define their own layering was the entire goal of my proposal. There are enough different use-cases being discussed I would expect predefined layers to backfire. Authors will use them differently on different projects, hacking them to solve problems they weren't "approved" for. The same way we hack specificity and importance to achieve these things today. Without the ability to create arbitrary layers, there is no way to "encapsulate" anything, and we're back to a global-namespace hack-layering war that this proposal is meant to address.

Assuming everyone will be fine sharing three globally predefined (specificity) layers is exactly the current problem.

Specificity/layer depends on method of importing

Nesting doesn't change the specificity of a layer. Nesting is only a way to name and group layers, nothing more. Adding multiple anonymous layers via import has no actual impact on the resulting weight of a style.

AIUI the current gist proposes that defined layers have higher specificity than un-layered style rules.

Check again. Unlayered are the highest "normal" layer, and the lowest !important layer. Explicit layers would only override existing styles when !important is applied. This could still potentially cause an issue for someone upgrading 3rd-party tools without checking? But that's already a danger with specificity and importance. The danger here is not new, we're only offering a possible new solution to this existing problem.

See also: authors accidentally forgetting a helpful line of code, or triggering layout jank via lazy-load.

Inter-file specificity conflicts already exist. They already take source-order into account as a meaningful cascade metric. Lazy-loading CSS can already trigger jumpy rendering, purely based on selector specificity and the source-order of imports. But the only solution right now is to manipulate & delicately balance selectors. That's a hack, and it makes the problem even more fragile. It breaks the semantics of selector-specificity, and the resulting code does not in any way convey the layering intended.

It would be great if authors had a way to solve that problem with a small change to their import declaration, or a single line of code that makes the layering explicit. That's what we're trying to do. We can't make every possible cascade issue impossible. We can provide more flexible, semantic, and robust tools for authors to address these issues when they come up.

chrishtr commented 4 years ago

Check again. Unlayered are the highest "normal" layer, and the lowest !important layer.

Ok great. I misread the gist then.

Nesting doesn't change the specificity of a layer. Nesting is only a way to name and group layers, nothing more. Adding multiple anonymous layers via import has no actual impact on the resulting weight of a style.

Yes, but the name of the layer depends on its import method, right? Therefore it determines the order of application of style rules due to the ordering of the layers.

Inter-file specificity conflicts already exist. They already take source-order into account as a meaningful cascade metric. Lazy-loading CSS can already trigger jumpy rendering, purely based on selector specificity and the source-order of imports. But the only solution right now is to manipulate & delicately balance selectors. That's a hack, and it makes the problem even more fragile. It breaks the semantics of selector-specificity, and the resulting code does not in any way convey the layering intended.

I agree that versions of this problem already exist.

mirisuzanne commented 4 years ago

the name of the layer depends on its import method, right? Therefore it determines the order of application of style rules due to the ordering of the layers.

The method of import can be used to add (and optionally name) a grouping layer around the file contents, if that's what you mean.

It sounds like you see that as a downside? I see that as a way to give the entrypoint file final control over how all layers will be used, which will help authors avoid global naming conflicts. This way a third-party tool is able to hide or expose whatever layers they want, and the consuming document can decide to either interact with those individual layers, or encapsulate them inside a namespace to avoid conflicts.

In either case, with or without an layer-import syntax, the importing document would be able to re-order any exposed layers inside the imported file. By providing an explicit syntax, authors have more control over how that interaction should or should not happen.

tabatkins commented 4 years ago

Yes, it's very important that the final page author have the ultimate control over how things are layered. A third-party library gives its layers a default ordering according to the order they appear in that library, but the page author can choose to reorder those (or rather, insert their own layers between the third-party's layers) if they have a need for that.

The importing syntax used is just about whether you wrap the entire third-party sheet in another layer or not; doing so lets you (a) put CSS that's not layer-aware into a layer, so it works better with the rest of the layer-aware page, and (b) easily put a whole stylesheet into a particular spot in the layer ordering, without having to carefully control the location it shows up in the document or care about what layer names the stylesheet might itself use.

chrishtr commented 4 years ago

The method of import can be used to add (and optionally name) a grouping layer around the file contents, if that's what you mean.

It sounds like you see that as a downside?

Not necessarily, I'm just saying this may be a source of developer confusion because of examples like the one I gave in the gist. (I'm not sure this is a big problem, I'm just going through all of the possible concerns and corner cases of this proposal to understand it better and discuss.)

css-meeting-bot commented 4 years ago

The CSS Working Group just discussed [css-cascade] Custom Cascade Layers (formerly "custom origins"), and agreed to the following:

The full IRC log of that discussion <dael> Topic: [css-cascade] Custom Cascade Layers (formerly "custom origins")
<dael> github: https://github.com/w3c/csswg-drafts/issues/4470
<dael> miriam: Take everyone through the proposal?
<dael> astearns: Yeah. Summary would be helpful
<fantasai> Proposal: https://gist.github.com/mirisuzanne/4224caca74a0d4be33a2b565df34b9e7
<dael> miriam: Starting at the top, first question is where in cascade would author custom origin layer fit. Felt it could achieve all goals as higher then specificity. Putting it above shadow dom creates additional problems so putting it between solves problems
<astearns> zakim, open queue
<Zakim> ok, astearns, the speaker queue is open
<dael> miriam: Question about shadow dom at bottom of doc
<dael> miriam: Interacting with !important we suggest layers exist entirely within defined origin. They work like orgins so reverse in !important layers.
<dael> miriam: normal layers styles not explicit are at the top followed by named layers in order set up
<dael> miriam: Reversed in importat so unlayers are at bottom.
<dael> miriam: Keeps meaning and intent of !important more clear and working similar but internally
<dael> miriam: Talked about style attribute and suggestion is it continues to be above these layers. Have to redefine but I think has to anyway with scope being removed. Keeps style attribute like other styles.
<dael> miriam: Levels for managing layers; does it happen at selector or declaration or block or importing. Suggesting block similar to MQ and as with existing @rules blocks can nest. Then layering based on order of first appearance in code
<dael> miriam: Example reset-base components reset. reset lowest, base on top of that. Order they're first discovered is order of layering
<dael> miriam: In terms of ordering layers there's a way to cheat and declare them all. Could be with empty blocks but also proposing layers shorthand to let you define. That's above imports
<dael> miriam: Suggesting an import syntax. Way to name and group everything inside a file. Helps with encapsulation. Bootstrap exposes layers but we can create a wrapping alyer that keeps all boostrap inside. Nesting doesn't impact order, just naming
<dael> miriam: Various discussion on syntax and if it builds on @import or unique
<dael> miriam: Nesting doesn't change order, jsut groups. If wrapping layer has name can interact with nested layers by calling that with some syntax for getting to layers within layers.
<dael> miriam: By allowing unnamed layers allow a tool like bootstrap to have private layers. Wrap in unnamed layer and removes ability to call them later.
<dael> miriam: More detail in the proposal
<dael> miriam: Migration path since this is between specificity and style this can be mimiced with specifiity so clearest way to show is list of IDs.
<dael> miriam: Can be more clean using IS to get specificity of IDs. Path to polyfill
<dael> miriam: Weakness on refactor use case. Since layers reverse in !import not idea but doesn't seem changes are extensive. Have to do something with !import styles in legacy to make sure they don't override in new. Not a perfect solution but works with a little manipulation
<dael> miriam: Questions from thread, does load order of stylesheet matter so if a sheet is lazyload but lazyload first does it matter? no.
<dael> miriam: Can you nest layer blocks, yes. Specificity is unchanged inside layers. Just subsuming it.
<dael> miriam: If stylesheet is in multi layers it's in multi which is true currently
<dael> miriam: Best syntax is open question still. Are unnamed layers feature or a problem? Allow hiding which is risky but powerful.
<dael> miriam: Way to revert layers? Do we want that?
<emilio> q+
<dael> miriam: How does light dom layers affect shadow dom layers with same name. Complex but think a wrapper may be able to resolve that powerfully
<astearns> ack emilio
<dael> emilio: regarding shadow dom; how does it matter? rules in different trees are sorted diff on top of specificity. I don't know how that is a problem.
<dael> miriam: Comes into play b/c ordering of names determining the layering order
<dael> miriam: If there are layers inside shadow dom named main and base and layers outside named base and main which order are they used?
<fantasai> +1 to scoping layers to a shadow context
<miriam> +1
<dael> emilio: I see. I think layers should scope to a tree. Inside a tree is where you sort by specificity. Doens't make sense to me to have names interact between trees. I think that's wha tyou prop with anon.
<dael> miriam: Could be interesting to let you access layers inside a shadow dom.
<dael> emilio: Why would you want? But does seem different discussion
<dael> astearns: My prop is get this proposal into the spec and start opening issues to dig through
<dael> astearns: Objections to move this proposal into the spec?
<dael> RESOLVED: move this proposal into the spec
<dael> astearns: Doesn't mean we're done, means we open separate issues to discuss each bit instead of whole. Thanks so much miriam for putting this together.
mirisuzanne commented 3 years ago

Initial editor's draft: https://drafts.csswg.org/css-cascade-5/ (sections 6.1 and 6.4)

mirisuzanne commented 3 years ago

Closed by CSSWG Resolution to publish FPWD.

frivoal commented 3 years ago

Uncovered some earlier notes about this topic, after @mirisuzanne had introduced and got enthusiasm about the topic, but before we had consensus on what the syntax ought to be. I don't think they're particularly relevant at this point, but just in case someone's looking for details on history and early stages, here they are: https://lists.w3.org/Archives/Public/www-archive/2021Jul/0007.html