Skip to content

[css-shadow-parts] Unifying ::part() and ::--foo #4900

@tabatkins

Description

@tabatkins

tldr: We should allow part names to also be selected as full custom pseudo-elements names, a la my-component::--heading in addition to my-component::part(heading).


In the Web Components virtual f2f today, we discussed custom pseudo-classes. In the thread about :state(foo) vs :--foo syntax, Maciej brought up the reasonable point that switching away from :state() to :--foo, but leaving parts as ::part(), is inconsistent.

I explained the reason behind maintaining ::part(): due to CSS's bad syntax choices in the past, pseudo-element syntax implicitly contains a combinator, moving the subject of the selector into the element's "pseudo children"; as a result, you can't stack pseudo-elements to apply multiple conditions to a single pseudo-element. However, we want to allow that for parts - we want a calendar widget to be able to expose day elements for styling with something like part="day weekend saturday week-1 us-holiday", and let stylesheets select on a combination of those qualities, like ::part(day weekend us-holiday)

However, Maciej's point is still valid! Also, people have wanted ::--foo syntax, to mirror normal pseudo-elements, and things like UA pseudo-elements (::-webkit-scrollbar-thumb, for example).

Jan's idea was to unify these - allow parts to be selected either with ::part(foo) or with ::--foo; if you only need to select on one property, you can use the more direct form, but ::part() remains if you have more complex selecting needs. This received general approval in the call, and I think it's a good idea personally. General thoughts?


I think there's one important detail to decide, which is how to handle existing part='' content. In general, CSSWG has made the decision that dashed-idents should be used consistently thruout APIs: you reference the --foo custom property with var(--foo), not var(foo), etc. In other words, the -- is part of the name, not a context-specific indicator of custom-ness.

Currently, tho, part='' allows any ident, not just dashed-idents. How should we deal with this? I see a few options:

  1. (I don't like this, it's bad, but for completeness:) part is unchanged. If you define part="foo", you can reference it as ::part(foo) or ::--foo. In other words, the -- is a context-specific indicator of custom-ness, but is not used when defining it, or referencing in ::part(). (Note: if you said part="--foo", it would be referenceable as ::part(--foo)or::----foo`.)

    This is backwards-compatible with existing code, but breaks consistency with how dashed-ident features are used everywhere else in CSS, both currently and planned. The component author doesn't make a choice on how to expose things, but the component user is presented with two fairly different names to refer to.

  2. part is unchanged. If you define a part as a dashed-ident, it can be referred to as a top-level pseudo-element. That is, given part="foo --bar", you can use ::part(foo), ::part(--bar), or ::--bar validly. ::--foo will not match, nor will ::foo.

    This is backwards-compatible with existing code, and consistent with CSS design principles for dashed-idents. However, it imposes a choice on component authors of whether they want to expose parts as top-level pseudos or only within ::part(). (There might be a good reason to make this distinction? Unsure, but I suspect not.)

  3. We restrict part to only allow dashed-idents. part="foo --bar" only defines one part name, --bar, which can be referenced as either ::part(--bar) or ::--bar. (The "foo" is ignored, as part of our forward-compatible error handling that silently drops things that aren't understood.)

    This is not backwards-compatible; virtually all existing content would become invalid and stop defining parts. However, it means there's no choice on the component author's side, and the component user has a closer relationship in the two syntaxes.

I slightly lean toward 3, but I'm unsure how much back-compat pain we want to take on. I'm fine with 2 if it's preferred by others. I'll fight against 1, it's terrible.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions