Styling in the Shadow DOM With CSS Shadow Parts
Safari 13.1 just shipped support for CSS Shadow Parts. That means the ::part()
selector is now supported in Chrome, Edge, Opera, Safari, and Firefox. We’ll see why it’s useful, but first a recap on shadow DOM encapsulation…
The benefits of shadow DOM encapsulation
I work at giffgaff where we have a wide variety of CSS code that has been written by many different people in many different ways over the years. Let’s consider how this might be problematic.
Naming collisions
Naming collisions between classes can easily crop up in CSS. One developer might create a class name like .price. Another developer (or even the same one) might use the same class name, without knowing it.
CSS won’t alert you to any error here. Now, any HTML elements with this class will receive the styling intended for two completely different things.
Shadow DOM fixes this problem. CSS-in-JS libraries, like Emotion and styled-components, also solve this issue in a different way by generating random class names, like .bwzfXH
. That certainly does help avoid conflicts! However, CSS-in-JS doesn’t prevent anybody from breaking your component in other ways. For example…
Base styles and CSS resets
Styles can be applied using HTML element selectors like and
/* This will have no effect on buttons inside shadow DOM */
button { background-color: lime !important; }
I wouldn’t say it’s good practice to style elements this way, but it happens. Even it does, those styles will have no effect on the shadow DOM.
It’s worth noting that inheritable styles like color
, font
and line-height
are still inherited in a shadow DOM. To prevent that, use all: initial
or, preferably, all: revert
once it has better browser support.
Let’s look at a common example of CSS applied directly to HTML elements. Consider this code from Eric Meyer’s reset:
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
What if the component we’re working with makes use of the user agent’s default values for margin and padding? This reset might cause it to appear broken since those defaults are effectively wiped out.
Shadow DOM is a way to avoid these problems. Shadow DOM allows us to feel fully confident that a component will render as expected, regardless of what codebase it ends up in. Equally, none of the code meant only for a component can inadvertently affect anything else — all without resorting to onerous class naming conventions. Shadow DOM offers a level of encapsulation that can’t be achieved any other way.
Encapsulation is great, but we also want our components to be themeable and customizable. That’s been made far easier with the ::part
selector.
Styling shadow DOM with ::part()
Until now, the only way for CSS to modify the styling of a custom element from outside of the shadow DOM was to use CSS custom properties. In a strict design system where you only want to allow limited changes, that might be ideal. If you want your component to be more versatile, it creates a problem. Every CSS property you want to offer up for styling needs to be defined using a custom property. Just the sound of that seems tedious.
The situation is compounded further if we want to style a component differently based on pseudo-classes, like :hover
. Basically, we end up with loads of custom properties. Let’s look at an example from Ionic, an open source set of web components. Just look at all the custom properties defined on the Ionic button component.
Go ahead, I’ll wait.
I counted 23 custom properties. Needless to say, that’s less than ideal.
Here’s an example using ::part()
to style the element instead.
In this Pen, I’m simply changing the color
, border
and background-color
properties, but I could use whatever I want without being constrained by what custom properties have been defined. Notice that I can also style different states of the part using pseudo-classes, like :hover
and :focus
.
The entire component in this button example is being exposed for styling, but if your web component consists of multiple HTML elements, you can expose only selected parts of the component to this sort of styling — hence the name ::part
. This stops users of the component from styling any arbitrary element inside the shadow tree. It is up the the component author to expose the parts of the component they explicitly want to. Other parts of the component can be kept visually uniform or make use of custom properties for a more minimal customizability.
So, how do we set this up for our own components? Let’s look at using ::part
to make certain elements of a web component eligible for styling. All we do is add a part attribute on the element we want to be exposed.
<div part="box">...</div>
<button>Click me</button>
In this example the div is customizable with the full gamut of CSS — any CSS property can be changed. The button, however, is locked down — it cannot be visually changed by anybody except the component author.
And the same way an HTML element can have multiple classes, an element can have multiple part names:
<div part="box thing">...</div>
So that’s what we get with ::part
: by exposing “parts” of an element we can provide some flexibility in how a web component is used while exercising protection in other areas. Whether it’s your design system, a component library, or what have you, the fact that CSS Shadow Parts are becoming mainstream gives us yet another exciting tool to work with.
The post Styling in the Shadow DOM With CSS Shadow Parts appeared first on CSS-Tricks.