Open LeaVerou opened 1 year ago
All of those targeting options sound AMAZING! 😍
When you say "make it a combinator", what does that really mean? Is that a syntax change? Is that an interpreter change? Something else?
If this is an interpreter change, is there room to as part of this investigation to look into ways that we might alter the specificity of a ::slotted()
selector? By default, it is very low, which leads to usage of the !imporant
flag when attempting to "require" certain visual delivery of slotted content. Being able to manage this more explicitly via the selector in some way would prevent needing to use !imporant
across a myriad of rules.
If this allows for targeting descendants, is there any benefit to investigating the inclusion of :host
in this conversation as well? It also refers to the light DOM, and while we've recently seen Safari add support for the :has()
selector to it, there are many other concepts that could be productive added to it (e.g. :host-context
equivalent selection) that would be looked on quite favorably by the community.
One other ::slotted()
-centric question that I've seen recently is the quest for :has(::slotted(*))
. Any change these two ideas vibe together?
All of those targeting options sound AMAZING! 😍
When you say "make it a combinator", what does that really mean? Is that a syntax change? Is that an interpreter change? Something else?
Combinators are things like >
, +
, ~
etc. They are used to target elements based on their relationships.
If this is an interpreter change, is there room to as part of this investigation to look into ways that we might alter the specificity of a
::slotted()
selector? By default, it is very low, which leads to usage of the!imporant
flag when attempting to "require" certain visual delivery of slotted content. Being able to manage this more explicitly via the selector in some way would prevent needing to use!imporant
across a myriad of rules.
The problem is not that its specificity is low, but that it operates on an entirely different level of the cascade. @mirisuzanne, @fantasai and I were discussing how to address this just the other day, but it's orthogonal to this. If you have any pointers to discussions about this problem (besides the issue I linked to above) they would be useful.
If this allows for targeting descendants, is there any benefit to investigating the inclusion of
:host
in this conversation as well? It also refers to the light DOM, and while we've recently seen Safari add support for the:has()
selector to it, there are many other concepts that could be productive added to it (e.g.:host-context
equivalent selection) that would be looked on quite favorably by the community.
This is also a separate issue, but any pointers you may have are useful, so we can open a new issue to discuss it!
PS: I merged your two consecutive comments, hence the deleted comment notice below 😊
Yes, please. A thousands times yes.
Sorry for not being more clear here:
Combinators are things like >, +, ~ etc. They are used to target elements based on their relationships.
Does that mean we would alter the interpretation of ::slotted()
or pair it with something that altered the syntax of :host ::slotted(input)
to (pretend use %
) :host % input
? I'm looking to see what you're thinking regarding the size of the delta here: plug-n-play, additive, breaking, etc.
I'll hunt down related issues/content on those other points, as I know they've been brought up in other contexts, I just can find them just this minute.
This would solve so many problems our design systems team has come across. I could list 5 reports just from the last week!
Does that mean we would alter the interpretation of
::slotted()
or pair it with something that altered the syntax of:host ::slotted(input)
to (pretend use%
):host % input
? I'm looking to see what you're thinking regarding the size of the delta here: plug-n-play, additive, breaking, etc.
Not sure I fully understand your question, but we can certainly not remove ::slotted()
(yet) or alter its interpretation as that would cause compat issues, this would be an addition. If once the combinator is well established, ::slotted()
usage drops below a certain theshold, maybe we could remove it then.
Btw no need to use a placeholder symbol, for something like this I don't think more ascii art is appropriate, something like /slotted/
is fine.
One small downside of a combinator is that you need to target the element the combinator operates on, i.e. the slot element, which makes for somewhat awkward syntax e.g. slot /slotted/ input
or just * /slotted/ input
instead of ::slotted(input)
.
This would solve so many problems our design systems team has come across. I could list 5 reports just from the last week!
Please do!
A major issue we're running into is lightdom (i.e. app-level) css interfering with our component styles.
There are some cases where we can ::slotted(thing) { what: ever !important; }
but we can't at all do that with nested content. So we have to choose between flexible components or effective distribution, a choice which is liable to produce neither outcome.
We're offering workarounds with our lightdom stylesheets, but we'd prefer not to have those at all, and just do everything in shadow DOM, which would be easier to use, more performant, and more maintainable.
Maybe @heyMP would like to elaborate
A major issue we're running into is lightdom (i.e. app-level) css interfering with our component styles.
I see. So, making ::slotted()
a combinator wouldn't directly help with that (the problem is that shadow DOM styles operate on a different level of the cascade), though it would make it easier to address it, since with this approach at least you're targeting the element in question directly, and not through another element. I do plan to file a separate issue for this, so more info and use cases is certainly welcome.
yes, exactly. it would remove the need for more-error-prone lightdom stylesheets, and given a later proposal to somehow bump the priority of ::slotted, would allow us to write-once-run-everywhere at greater scale and pace.
As mentioned above, the low specificity of ::slotted()
causes us a lot of pain. Any CSS reset file wipes out our styles which forces us to use !important
everywhere.
Other than that, the ability to have complex selectors will go a long way. We could eliminate the requirement of having to load a lightdom.css file just to target elements in an unordered list. 😀
Is there overlap with https://github.com/w3c/csswg-drafts/issues/7346 ?
Is there overlap with #7346 ?
Yes, but the ability to add combinators after ::slotted()
would only solve some of the issues with it, not all.
This is probably out of scope for this ::slotted()
discussion but I'll give this issue some visibility also just in case. Keyframe animations defined in the parent shadow roots aren't available for ::slotted()
children.
This animatein
keyframe won't work.
:host([animate]) ::slotted(*) {
animation-name: animateIn;
}
@keyframes animateIn {
0% {
opacity: 0;
transform: translate(0, -100%);
}
100% {
opacity: 1;
}
}
@heyMP Very interesting! I hadn't considered a use-case like this yet but it makes perfect sense. Keyframes are CSSRules and so it's being defined inside a different tree than the light DOM content.
As mentioned above, the low specificity of ::slotted() causes us a lot of pain. Any CSS reset file wipes out our styles which forces us to use !important everywhere.
@heyMP Regarding this point, I have definitely felt this pain in the past though on other projects, I quite like and embrace that low specificity. Makes us evaluate what kind of light/shadow DOM support a component (or library) wants to enforce.
@heyMP @bennypowers Did I set up this demo on par with what you found in your keyframes example? https://codepen.io/castastrophe/pen/rNKjLVj?editors=0010
I'm finding that the keyframe defined inside the shadow DOM applies to ::slotted(*) and to the slot itself just fine.
Dove deep into this last night. Here's the summary of our findings:
Keyframe defined in the shadow DOM scope:
Keyframe defined in a style tag assigned to the slot:
Found no examples of a keyframe defined in the global DOM scope crossing the boundary into the shadow DOM.
With that, I'll stop squirreling on this topic as it feels unrelated to the primary goal laid out in the description.
With that, I'll stop squirreling on this topic as it feels unrelated to the primary goal laid out in the description.
Can you open a new one though? This is an important topic that deserves separate consideration.
The CSS Working Group just discussed [css-shadow-parts] Make `::slotted()` a combinator
.
@LeaVerou Won't this allow .a /slotted/ .b + .c
to select elements outside the slotted subtree from within the shadow?
I can’t help but find /slotted/
as the proposed combinator to be “not ideal”. It’s quite long and deviates from what other combinators look like – it feels like “the odd one out” in the list of other combinators. Maybe we can find some relevant ASCII symbol to represent it? Earlier in the thread %
was already proposed, but personally I was thinking of using $
for it.
/foo/
is the syntax we decided on some time ago for future combinators; we're basically out of good ASCII to use. Yes, it's weird because it's the first, but that'll be the case for any of them.
(The remaining ASCII glyphs are almost certainly more valuable to save for new simple selector prefixes rather than combinators.)
It’s quite long and deviates from what other combinators look like
Well once upon a time there was /deep/
in Web Components V0 in Chrome ¯\_(ツ)_/¯
The CSS Working Group just discussed [css-shadow-parts] Make `::slotted()` a combinator
.
I'd like to outline a concrete use case.
For example, suppose we want to have a <foo-layout>
element that can style children and nested children a certain way. Suppose we use it like this (contrived names for sake of example):
<foo-layout>
<foo-layout-item>foo</foo-layout-item>
<foo-layout-item>bar</foo-layout-item>
<foo-layout-item>baz</foo-layout-item>
</foo-layout>
and somehow it lays out the children in a layout. In this example, children are slotted to the default slot.
The example above is totally doable, not problem so far.
Now suppose we also want to define a nested syntax for the layout features. For example:
<foo-layout>
<foo-layout-item>foo</foo-layout-item>
<foo-layout-item>
bar
<foo-divider></foo-divider>
baz
<foo-divider></foo-divider>
lorem
</foo-layout-item>
<foo-layout-item>ipsum</foo-layout-item>
</foo-layout>
where in this example, the <foo-divider>
can be use specifically as a child of a <foo-layout-item>
that is a child of the <foo-layout>
to achieve a certain effect. Also imagine that <foo-divider>
can be used completely outside of <foo-layout>
for other use cases and with differing effects.
In that example, we currently cannot apply a special style to the foo-divider
element from the foo-layout
's shadowroot styles.
The style that we would want to write, from inside of the foo-layout
element's ShadowRoot styles, would look like the following with hypothetical syntax that is currently not supported:
::slotted(foo-layout-item) > foo-divider {
/* Target any foo-divider that is direct child
of a slotted foo-layout-item, to give it some specific
sizing within the specific layout context, for example */
}
It would be possible to write style inside of the foo-divider
's shadow dom using host-context()
, but that would not be ideal. Imagine that instead of <foo-divider>
we actually are using <sl-divider>
from Shoelace. We do not want to arbitrarily inject host-context()
styles into 3rd party elements, as that would be very hacky (if their roots were closed, that would be even hackier).
We want higher-up elements (<foo-layout>
in the example) to be able to style light DOM in certain ways such that the child elements do not necessarily need to be aware of every single type of higher-up element they could be nested inside of.
In real-world practice, I have been writing new elements in my app using Shoelace, and in some of those higher-level layout elements, I want <sl-divider>
certain ways (as opposed to make a new divider element).
Another way to achieve this is to make N different lower-down elements to use with N different higher-up elements. What I mean is, I could make
<foo-layout>
along with <foo-layout-divider>
to work specifically together,<foo-name-tag>
along with <foo-name-tag-divider>
to work specifically together, but it would be more practical to make a single <foo-divider>
that works differently (and intuitively) in various contexts, and I'd want to be able to do this with 3rd party elements easily (I am writing new layout elements, but I don't necessarily own <foo-divider>
, and I don't want to have to re-implement <foo-divider>
features in whole new elements).
@trusktr the parallels between your use case and being able to accept native elements into slots like <details>
, <dt>
, <ol>
, <table>
, <ul>
run quite high. It makes me wonder at which point we could request/expect actual numbers on the cost of this sort of selecting from
Implementors and not just allegorical numbers see on the initial v0 implementations in 2012.
While the underlying technique between this and @scope
are likely different, it would seem that they should embody similar issues in this area. Is that API targeted to be bound by the same restrictions? What can be learned from that which may allow us to make preciously enforceable advances in this area?
I’d go so far as to volunteer time to investigate this more deeply if I could find some one versed in showing me a baseline as to how to get started working with one of the engines and a baseline of the associated selector hashing from which to work. I’d love this to be the next container queries, some thing we could never do… until we did!
Currently
::slotted()
is a functional pseudo-element, which causes no end of problems::has(/slotted/ *)
Making it a pseudo-class is not an option, as discussed in https://github.com/w3c/csswg-drafts/issues/7305 . However, unless @fantasai and I are missing something, it should be possible to make it a combinator (
/slotted/
), which would address all of the above.::part()
has similar issues, but is not equally trivial to turn into a combinator because that would give the outside world access to too much of the shadow subtree, but::slotted()
refers to the light DOM so doesn't have these problems.