Shadow drops

The most obscure of the three technologies behind web components, the Shadow DOM allows you to encapsulate code in a different document structure. The separation is particularly refreshing in terms of styles: CSS properties stated in the nested markup don’t escape the new scope, while key-value pairs in the light DOM do not, regularly, sneak in.

In the CSS standard you also find a host of provisions for more advanced styling, in the form of dedicated pseudo classes and elements. And one in particular deserves an addendum. Not to mention a small example.

Starting from virgin soil, consider a custom element extending the generic HTMLElement interface.

class ShadowRose extends HTMLElement {
	constructor() {
		super();
	}
}

In the constructor function we enter the shadows with the attachShadow function, and sprinkle a small vector graphic in the dark DOM.

const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<svg viewBox="-25 -51 50 90">
	<g fill="hsl(0 0% 98%)" stroke="hsl(0 0% 98%)">
		<!-- ... -->
	</g>
</svg>`;

I’ll spare you the details of the actual drawing, but look at the group element for a moment. Here I set the fill and stroke attributes to a nearly white color. This means that the nested shapes are painted with the bright pick.

Once you define the custom element.

customElements.define('shadow-rose', ShadowRose);

Adding the matching tag in plain markup is enough to grow the first graphic.

If you want to paint the shapes with a different color, you have the option to expose the group with the part attribute.

<g part="rose" fill="hsl(0 0% 98%)" stroke="hsl(0 0% 98%)">
	<!-- ... -->
</g>

Assign a label, similarly to a class, and from the outside the ::part pseudo element is able to target the node and update the properties.

::part(rose) {
	fill: hsl(1 93% 58%);
	stroke: hsl(1 93% 58%);
}

With the syntax you are therefore able to paint from the light DOM, and in bright red.

This is the essence of CSS shadow parts, but admittedly, doesn’t tell the full story.

Say you create another custom element, itself with a shadow root.

class ShadowBouquet extends HTMLElement {
	constructor() {
		super();
		const shadowRoot = this.attachShadow({ mode: 'open' });
	}
}

If you were to mark up the element with one, two, or even three copies of the first custom element, you could wrap the graphic in a cute composition.

shadowRoot.innerHTML = `
<shadow-rose></shadow-rose>
<shadow-rose></shadow-rose>
<shadow-rose></shadow-rose>
`;

But this time, you would also lose the red color. This you may dread, but the combination of part attribute and ::part pseudo element is not enough to surface the markup with nested shadows.

If you want to expose the node, you have to be explicit, to formally declare your intention with the exportparts attribute.

shadowRoot.innerHTML = `
<shadow-rose></shadow-rose>
<shadow-rose exportparts="rose"></shadow-rose>
<shadow-rose></shadow-rose>
`;

Adding the string on a custom element works, giving you one more way to walk through the trees and style the shadows. Even nested ones, where light barely shines.

Of course, light does shine between the documents in at least two manners.

The first is with inherited properties, such as font-family and, thankfully, color. In the context of SVG you cherish this option through the ever flexible value of currentColor.

<g fill="currentColor" stroke="currentColor">
	<!-- ... -->
</g>

Set the fill, set the stroke to respect the value and you can even spare the elaborate, multi-part scheme. The shapes will be painted with the color of the closest parent element.

body {
	color: hsl(1 93% 58%);
}

The second manner is with custom properties. These are designed to pierce through the Shadow DOM, so that in the group you can refer to the value held by the property.

<g fill="var(--color-rose)" stroke="var(--color-rose)">
	<!-- ... -->
</g>

And even provide a fallback, which will take its place should somebody forget to set the property.

<g fill="var(--color-rose, hsl(1 93% 58%))" stroke="var(--color-rose, hsl(1 93% 58%))">
	<!-- ... -->
</g>

Should somebody forget the preferred color.