Re: [csswg-drafts] [css-cascade] How do custom origins interact with Shadow DOM (#4984)

To elaborate on this issue:

As you know, Shadow DOM isolates elements from outside styling - by default, the stuff in a shadow isn't visible to selectors in the outer document, and this applies further as you nest, so each component's DOM is only styled by the stylesheets in the same shadow tree. So far, simple.

But we have ::part(), which allows outer scopes to apply styles to elements in shadows. So we have to decide which rule wins when there's a conflict.  We rejected just using specificity as normal, since the outer and inner scopes aren't necessarily designed together, and thus have no good way to avoid annoying interleaving errors with specificity.

Instead, we decided to add a new layer, between specificity and origin: scope. In the non-important origins, outer scopes win over inner scopes; `::part()` styles will always defeat the component's own styles, because it's assumed that using::part() for customization *should* be more considered higher-priority than setting up default always-on styles inside the component. The ordering is reversed in !important origins; `::part()` styles lose to the component's own styles, because presumably the component knows the *required* bits of its style better than the outer page does, and so can guard them by making them !important.

A given element can have more than two scopes competing over it, too: if a component nests an inner component, and the inner component exposes a `part`, then the outer component forwards that part onto itself, then you can have: (1) the inner component's own style, (2) the outer component using ::part() to style the inner component's element, and (3) the outer page using the forwarded ::part() to *also* style the inner component's element.  And all this has the reverse cascade in !important styles, too; potentially six different origin+scope combinations to puzzle thru when deciding which rule should win.

(*Effectively*, we're just adding arbitrary additional origins, arranged in the standard way based on a priority-of-constituencies approach. Just like author styles win over user styles, because they're assumed to be more specific and thus more likely to be correct in a given instance, styles from ::part() win over styles from the component. And !important inverts the relationship for the same reason.)

So! If custom origins uses the actual Origin machinery, then this increases all that complexity. Rather than 2*N origin+scope combinations (where N is the depth of the shadows), we'd have M*N combinations (where M is the total number of origins, at least 2). That gets complicated, both for authors and for implementations. It would also mean that scope is no longer a strong determiner of the winner: a higher-origin style in a component would beat a lower-origin style from `::part()`; it's not clear if authors would expect this or not.  (The "shadow scope is effectively additional origins" idea becomes very muddled; the "scoping origins" get duplicated into every custom origin, and you get a probably-confusing interleaving of styles. I think it matches author mental models better for scopes to be "more important" in the cascade than custom origins are, so a component user can confidently predict whether their ::part() styles will win over a component's own styles.)

If we instead do something like I suggest in <https://github.com/w3c/csswg-drafts/issues/4981#issuecomment-622090790>, moving this functionality into a new specificity category, we avoid the complexity problem; we retain just 2*N scope+origin combos. We also maintain the current "::part() beats component styles" behavior, which I *suspect* is better for authors.

Elika does bring up some counter-points to that idea, tho, so more thought is needed.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/4984#issuecomment-622190214 using your GitHub account

Received on Friday, 1 May 2020 00:29:54 UTC