Firefox 72 has been released with some new APIs, and two of those are good news for the Web Components users. The first one is FormDataEvent
and event-based form participation, previously only available in Chromium. But today I would like to focus on another feature, implemented behind a flag since Firefox 69: part
HTML attribute and ::part
pseudo-element.
CSS Shadow Parts proposal is among the most waited additions to the standard for developers who create customizable components and design systems. It is a new API for styling individual elements in Shadow DOM from outside. This lets to make a single instance of certain UI widget look slightly differently – an ability that we often need in the real world apps.
Unlike CSS Custom Properties, which only allow to configure individual values like --primary-color
, a new ::part
pseudo-element give us a true flexibility we have been longed for. At Vaadin, we adopted the idea of CSS Shadow Parts already in 2017 as explained in this talk on theming. And now we are happy to see that ::part
finally gets cross-browser support.
History
The idea of custom pseudo-elements, also known as CSS Shadow Parts, has been discussed at least since 2015 but the agreement was reached in early 2017 when it was decided to abandon CSS mixins and @apply
proposal (never released in Chrome), and to implement ::part
instead. Three years after – not too long for web standards – and we are getting there in 2020.
One note for those who remember a fancy explainer by Monica Dinculescu: the spec has changed since then. There is now an updated version by Fergal Daly. Also, today I'm only talking about ::part
because ::theme
, another pseudo-element from the original proposal, has been postponed. There are ongoing discussions related to ::theme
and its future is rather unclear.
But the status of ::part
looks very promising. It is supported in Chrome since version 73 and now also in Firefox 72. Safari has already implemented CSS Shadow Parts in Technology Preview 94, and it remains to wait for a stable version to be released – same as for Chromium-based Edge. So it's already time to experiment with ::part
and learn how to use it in practice.
Usage
Basic example
According to the comment by Tab Atkins, one of the spec editors, the point of ::part
is to hide the internal details of the web component, and to only expose exactly the parts the component author explicitly wants to.
Let's go directly to the example. The embedded CodePen below provides an example of styling <vaadin-details>
custom element using ::part
. This is a simple component which exposes a few elements using part
attribute.
Looks like good old vanilla CSS, doesn't it? Even if you open this demo in a browser that doesn't support ::part
, nothing will explode: the component will remain fully functional with the styles that it provides out of the box.
Using CSS Shadow Parts in simple cases like this one it's cheap. In fact, you only need to add part
HTML attribute on the elements you wish to expose. This leaves a component consumer to choose whether to use this API or not.
Using ::part
with pseudo-elements
As you may noticed, the above example demonstrates how the details toggle button can he styled using the ::part(toggle)::after
CSS selector. We can use ::part
this way in combination with any other pseudo elements, too.
As demonstrated in the CodePen above, all the browsers that implement ::part
already support this feature. However, please don't expect it to work with non-standard pseudo-elements like ::-webkit-scrollbar
.
Using ::part
with pseudo-classes
Another feature that is often needed for elements inside shadow tree is an ability to style certain states like :hover
. We can achieve it with ::part
– and that's how it naturally fits into the way of writing CSS we are used to:
While other user-action pseudo-classes like :focus
are also allowed to be used with ::part
, the spec clearly states that structural pseudo-classes (e. g. :first-child
, :nth-of-type()
, :empty
and so on) are not supported.
Path forwarding with exportparts
In certain cases, the parts we might need to expose for styling are located in a nested component, which has its own shadow root. That isn't possible with ::part
by default, as it does not recursively traverse shadow trees.
However, there is now exportparts
attribute designed to handle this case. When an element in shadow tree has this attribute, its value defines what parts are "exported" from that element and can be styled with ::part
:
As of today, this feature only works as a "whitelist". There was a proposal to also support forwarding with -*
to have kind of "wildcard". However, it is not currently standardized and therefore not implemented in browsers.
Feature detection
In some cases you might want to detect whether a browser supports CSS Shadow Parts or not – for example, to load a different chunk or .css
file with fallback styles. This can be done in JS using the following code:
'part' in HTMLElement.prototype; // true if "part" is supported
There is no cross-browser way to detect support for ::part
using CSS only because @supports selector()
API is currently only available in Firefox.
Limitations
While CSS Shadow Parts definitely bring us a lot of flexibility, they still has certain limitations that exist by design. The restrictions related to certain pseudo-classes that are disallowed after ::part
belong to them.
Same as ::slotted()
pseudo-element that is already familiar to anyone who uses Shadow DOM, ::part
is disallowed to use in complex selectors so you can't use it to style a sibling or child node – only the part itself.
Another important limitation: it's not possible to use classes or attribute selectors after ::part
– these are considered an implementation detail. One possible workaround for it could be to use multiple parts approach.
The :state()
pseudo-class is designed to work with ::part
and it should cover some use cases by allowing CSS like ::part(video):state(playing)
for styling custom elements that are exposed using part
attribute.
Documenting
CSS Shadow Parts can be considered public CSS API exposed by a web component. This means that they should follow semantic versioning – so removing or renaming any part
attribute requires a major version bump.
The web-component-analyzer tool supports @csspart
JSDoc annotation that can be used to document CSS Shadow Parts exposed by a custom element, and outputs them as part of experimental JSON format.
And with the help of the <api-viewer>
element (my pet project), you can generate a web component API docs from JSON, including CSS Shadow Parts. By the way, <api-viewer>
itself can be styled with ::part
.
Learn more
As long as CSS Shadow Parts are relatively new API, there aren't many examples yet. Here are few places where you can see ::part
in action:
Elix, a collection of web components that implement common UI patterns by Jan Miksovsky. Elix recommends styling element parts among other approaches for customizing elements appearance.
Chromium Web UI elements. These web components are in progress of being updated to using CSS Shadow Parts instead of CSS mixins. You can see them already used in several places like Chrome Settings.
Firefox UI. As mentioned in the recent blog post by Brian Grinstead, the process of re-building Firefox UI with Web Components resulted in prioritizing CSS Shadow Parts support among other related features.
The web-platform-tests suite. This project is very useful to understand how certain features are supposed to work. You can check the results to see how CSS Shadow Parts work in the latest browser builds.
Mozilla Developer Network already (MDN) provides two articles about
::part
pseudo-element andpart
global attribute. They also have a dedicated example in theweb-components-examples
project.
Finally, if you want to look into questions that are still under discussion, check out the CSSWG issue tracker with the corresponding label.
Summary
CSS Shadow Parts are new API that can be already used in some projects, especially in those cases when graceful degradation approach is acceptable. So I highly recommend evaluating part
and exportparts
to anyone who works on a web components library, UI kit or design system.
Top comments (4)
Your link for the
@supports selector()
is not correct. Here is the documentation on MDNThanks! Fixed.
Thanks for this article. I've been anticipating this feature for quite some time now and it's awesome to hear that it's being implemented.
Excellent article.