Repository navigation
[css-pseudo][css-shadow-parts] Define "part-like pseudo-element" concept #10083
Description
In the WHATNOT joint session with CSSWG today, we resolved that the new details pseudos would indeed be exposed as normal UA pseudo-elements (rather than ::part()s), but that we'd define a new concept of "part-like pseudo-elements" that grants the pseudo the abilities of ::part() that we want to preserve.
Namely:
- a part-like pseudo has the ::part() blocklist of a few pseudo-classes (the tree-structural ones) and allows all other pseudo-elements and pseudo-classes, rather than the normal pseudo allowlist of a few pseudo-classes (the logical-combination and user-action ones) and banning all the rest.
- a part-like pseudo has a well-defined inheritance relationship with the originating element and other part-like pseudos, rather than the normal "always inherit from the originating element" behavior.
- the
exportpartsattribute will gain a syntax for exporting part-like pseudos, details TBD - we categorize future pseudo-elements as "part-like" when appropriate, and do a review of existing pseudos for acceptability of retroactively labeling them "part-like" and giving them these new powers
IIRC, the only real point of contention left is that, for normal parts, you only have access to the parts declared or exported by the component itself. If you expose a sub-component as a part, you do not get access to its parts too (that is, you can't ever write my-el::part(my-foo)::part(their-bar)), specifically so we don't expose the fact that the part is implemented as a nested component rather than plain DOM. But parts do provide access to their pseudo-elements (you can write my-el::part(my-foo)::before), because that's useful and many pseudo-elements aren't remotely appropriate to be exported as parts themselves.
But these two points are now in conflict. If you can export a part-like pseudo to be one of your component's parts, but you can also access it as a pseudo-element, it means you can spell the same selector two distinct ways: my-el::part(my-content) and my-el::part(my-details)::details-content.
This does expose the fact that a part is a particular built-in element, but that's a fact we already explicitly exposed, so it's not a new information leak. (Any element-specific pseudo-classes also expose this.)
It also means there's two ways to refer to the same element, but that's generally true of selectors, so I don't find this a particularly compelling problem.
I suggest, then, that we simply accept this as a possibility. But we could try to work around it in a few ways:
- Block access to part-like pseudos from a part. But this wouldn't be retro-compatible if someone was already doing, say,
my-el::part(file)::file-selector-button, and it requires people to be very aware of the part-like pseudos exposed by any particular element. - Block access to part-like pseudos from a part if they were exported. So you could write
my-el::part(my-details)::details-contentsormy-el::part(my-contents)for a given element, but never both. This potentially allows for inconsistent behavior, where some pseudos are allowed (because they weren't exported under another name) but others aren't. - Block access to all the part-like pseudos from a part if any were exported. Same as (2), but now you're at least guaranteed that all the part-like pseudos are consistently shown or consistently hidden, and there's no back-compat issues like (1). But it still means that non-part-like pseudos are exposed on the part, along with any element-specific pseudo-classes, so you're still not meaningfully hiding the implementation details. And if we add more part-like pseudos to an existing element, they'd be blocked by default if a component exposed any of them, until the component was updated.
I think all three of these have downsides, and personally consider them to outweigh the downside of just "you can access it in two different ways". So I recommend not doing anything special for this.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsFriday afternoon
Activity
dbaron commentedon Mar 14, 2024
Note that a downside of "you can access it in two different ways" is that we need to define and implement cascading the two things together. (I don't think defining it is hard since I don't think they differ in context, and I think any specificity differences are well defined. Implementing it might be trickier.)
tabatkins commentedon Mar 14, 2024
Yes, same context, so that's not an issue. Cascading (ideally?) shouldn't be any more difficult than what's already possible, since you have to cascade, say,
::part(foo)with::part(foo):hover.tabatkins commentedon Mar 14, 2024
There was also a topic in the conversation about ensuring that existing pseudo-elements should be marked as part-like, when they represent something that's basically (or actually) a real element under the covers. Here's the full list of pseudo-elements that are currently defined, according to Bikeshed, and categorized according to my initial impressions:
Clearly should work
::after- it's just a box, you can do whatever to it::before- ditto::backdrop- ditto (mostly?)::part()- duh::slotted()- also refers to a real element, like an inverted ::partProbably should work
::file-selector-button- basically just a button, but might restrict its properties somewhat since the rest of the element's internals aren't definedProbably irrelevant and shouldn't work?
::view-transition- all of these live in a separate tree anyway, not part of any actual element (they're more pseudo-elements of the document), and when we have scoped VTs, while they'll be originating from some element they're still basically separate::view-transition-group()::view-transition-image-pair()::view-transition-new()::view-transition-old()Maybe should work?
::placeholder- defined with limitations so that it's kinda text-like, but in theory it could be just a box inside the input::marker- some questions to answer about exactly what its layout role is, but in principle it's just as "real" as a::before::cue- this set is all WebVTT stuff. The way they're defined, these are all basically tree-abiding, well-behaved boxes, but the "elements" are generated by WebVTT rather than DOM, and don't meaningfully exist in most ways. Because of this they're limited in what properties they can accept.::cue-region::cue-region(selector)::cue(selector)Clearly should not work
::attr()- represents an Attr node, not a layout object (just there for doing selectors in contexts where Attr nodes are relevant targets)::first-letter- all of these are text-ish, which generally aren't tree-abiding::first-line::grammar-error::highlight(<custom-highlight-name>)::postfix::prefix::selection::spelling-error::target-text::nth-fragment()- not quite tree-abiding - it lives in the box tree, but doesn't have a virtual existence in the DOM tree. Probably restricted in what properties it can take, etc.tabatkins commentedon Mar 14, 2024
Pinging @rniwa in particular for the above list, since they brought up this aspect in the call.
vmpstr commentedon Mar 15, 2024
Re
view-transitionpseudos: the one point that applies here is that they are currently specced to inherit from the parent vt pseudo element, not from the originating element. As you mentioned, in the future they could also be present on elements other than:root. Early in the design discussions for view transitions, we actually weighted the pros and cons of using shadow DOM vs pseudo elements for this tree construction, so vt is similar to both in how its usedI'm not clear on some of the points you mentioned that part-like pseudos would provide, but if they give an ability for shadow DOM to export them and be styled by the host, then it may be relevant to the discussion. And most of those elements do have boxes and possibly act like images
[css-pseudo] Define part-like pseudo-element. #10083
Add ::details-content pseudo-element.
Define "part-like pseudo-element", from #10083. (#10199)
Add ::details-content pseudo-element.
Add ::details-content pseudo-element. (#10379)
dbaron commentedon Aug 27, 2024
Part of the needed spec changes here have landed.
One part that I think has not landed yet is a definition that says that part-like pseudo-elements have the same rules as
::part()does for when pseudo-elements and pseudo-classes are allowed after them. (I'd note that I just filed #10786, #10787, and #10788 on these rules.)It's also possible that those rules should move to selectors rather than css-shadow-parts; that might also help with #10787.
dbaron commentedon Aug 27, 2024
I think there's also an open question about how to extend the rule that
::part(a)::part(b)is not allowed to part-like pseudo-elements. I think perhaps the right rule is that part-like pseudo-elements and::part()can be combined in a compound selector subject to the constraint that the compound selector has at most one::part(). But I'm curious if others think that's the right approach.dbaron commentedon Aug 28, 2024
Actually, I think that suggestion was more complex than needed. I think since pseudo-elements are built-in, and we aren't planning to expose parts on built-in things, there isn't any need for
::part()following part-like pseudo-elements. So I think probably the rule that we want is that::part()is not allowed after::part()or any part-like pseudo-element. Then we don't need any constraints about the entire compound selector and can continue to describe the rules in terms of "is X allowed to follow Y".tabatkins commentedon Sep 5, 2024
Yeah, just disallowing ::part() after a part-like is fine; part-likes are equivalent to ::part() and similarly don't want to expose further internal structure.
[css-shadow-parts][css-pseudo] Move various ::part() details to 'part…
tabatkins commentedon Sep 23, 2024
(And just to be clear, the inverse is not true; part-likes other than ::part() should be allowed after part-likes. That's already required for compat, since you can access various pseudo-elements on a ::part, and it's the right move anyway imo. This does require that we stick to our guns that built-in parts are always exposed as bespoke part-like pseudos, or at least are exposed as both bespoke pseudos and ::part() names.)
css-meeting-bot commentedon Sep 27, 2024
The CSS Working Group just discussed
[css-pseudo][css-shadow-parts] Define "part-like pseudo-element" concept.The full IRC log of that discussion
<dholbert> dbaron: this one, we already had a resolution, but I wanted to give a brief update since the state of things is confusing<dholbert> dbaron: so we resolved to make this :part-like pseudo-element concept
<dholbert> dbaron: half of the edits are edited in, but not all of them
<dholbert> fantasai: e.g. this edit, which has been waiting for this current discussion
<fantasai> -> https://github.com//pull/10839/files
<dholbert> dbaron: we've made some of the edits, to make partlikes like ::part
<dholbert> dbaron: earlier we were discussing what selectors and pseudo-classes apply after ::part
<dholbert> dbaron: those all apply to ::part-like as well, and I'm planning to make those edits
<dholbert> dbaron: Tab made edits about what properties apply. But the selector edits have not yet been made
<dholbert> dbaron: that's all
[css-shadow-parts][css-pseudo] Move various ::part() details to 'part…
yisibl commentedon Oct 24, 2024
This has been renamed to
element-backed pseudo-element.24eda7a#diff-4ffcf0741c5ab1b2bc788d6e2246559785c2a7e66c89ebc463224052170c2498R1602