Shadow DOM: Styles
Yesterday’s post was all about coding and structuring your first Shadow DOM elements. But I’m sure most of you were wondering, how do we style these things?!
The use of CSS in Shadow DOM is an interesting and large topic. So large, in fact, that I’m going to split it up over the next couple of posts. Today we’ll learn the basics of using CSS within the shadow boundary, and also how to style our shadow hosts.
Before we get started I wanted to thank Eric Bidelman for his amazing article on styling the Shadow DOM. Most of this article is my interpretation of his post. Definitely go read everything on HTML5 Rocks that pertains to Web Components when you get a chance.
Support
In order to try the examples I suggest you use Chrome Canary v31 or greater.
Also make sure you’ve enabled the following in Chrome’s about:flags
.
√ Experimental Web Platform features
√ Experimental JavaScript
I believe Shadow DOM is supported in Chrome without experimental flags but we may touch on other Web Component technologies that require them. Better to just turn them on now I think :)
Codez!
I’ve created a sketchbook for this post and future Web Components related stuff. You can grab the sketchbook on GitHub. For each of the examples that I cover I’ll link to the sketch so you can quickly try things out.
Style Encapsulation
Sketch 4: style-encapsulation
Astute readers probably noticed that I used a new term in the introduction for this post. That term is shadow boundary and it refers to the barrier that separates the regular DOM (the “light” DOM) from the shadow DOM. One of the primary benefits of the shadow boundary is that it prevents styles that are in the main document from leaking into the shadow DOM. This means that even though you might have a selector in your main document for all <h3>
tags, that style will not be applied to your shadow DOM element unless you specifically allow it.
Examples? Yes, let’s.
Here we have two buttons. One is in the regular DOM, and the other is in the shadow DOM. You’ll notice that the style tag at the top of the page instructs all buttons to use a cursive font and to have a font-size
of 18px.
Because of the shadow boundary, the second button ignores this style tag and instead implements its own look. We never specifically override the font-family
to change it back to sans serif, it just uses the typical browser defaults.
Keep in mind that the shadow boundary also protects the main document from the shadow DOM. You’ll notice that our shadow button has a color
of blue. But the button in the original document maintains its default appearance.
This kind of encapsulation is pretty amazing. For years we’ve struggled with style sheets that always seem to get bigger and bigger. Over time it can be difficult to add new styles because you’re worried you’ll break something elsewhere on the page. The style boundaries provided to us by the shadow DOM means that we can finally start to think about our CSS in a more local, component specific way.
Styling :host
Sketch 5: styling-host
I often think of the shadow host as if it’s the exterior of a building. Inside there’s all the inner workings of my widget and outside there should be a nice facade. In many cases you’ll want to apply some style to this exterior and that’s where the :host
selector comes into play.
Adding a red border to our widget doesn’t seem like much but there’s actually a number of interesting things happening here. For starters, notice that styles applied to the :host
are inherited by elements within the shadow DOM. So our <p>
ends up with a font-size
of 28px.
Also notice that the page is able to set the text-align
inside the :host
to center. The :host
selector has low specificity by design, so it’s easier for the document to override it if it needs to. In this case the document style for .widget
beats out the shadow style for :host
.
Styling by :host Type
Sketch 6: styling-host-by-host-type
Because :host
is a pseudo selector we can apply it to more than one tag to change the appearance of our component. Let’s do another example to demonstrate.
I’ve switched to using a template tag for this example since it makes working with the Shadow DOM a lot easier.
As you can see from the example above, we’re able to change the look of our component by matching the :host
selector to a specific tag. We can also match against classes, IDs, attributes, etc. Really any valid CSS will do.
For instance, you could have .widget-fixed
, .widget-flex
and .widget-fluid
:hosts
if you wanted to build a highly responsive component. Or .valid
and .error
:hosts
for form elements.
By using the *
selector we’re able to create default styles which will apply to any :host
, in this case setting all components to a font-size
of 24px. This way you can construct the basic look for your component and then augment it with different :host
types.
What about theming hosts based on their parent element? Well, there’s a selector for that too!
Theming
Sketch 7: theming
Using :host()
syntax we’re able to completely change the look of our widget based on the containing element. This is pretty neat! I’m sure you’ve all used the child selector before, .parent > .child
, but have you ever wished for a parent selector, .parent < .child
? Now it’s possible, but only with the shadow DOM. I wonder if we’ll see this syntax tracked back to normal CSS someday?
Styling :host States
Sketch 8: styling-host-states
One of the best uses of the :host
tag is for styling states like :hover
or :active
. For instance, let’s say you want to add a green border to a button when the user rolls over it. Easy!
Nothing fancy but hopefully it gets your imagination going a bit. What other states do you think you could create?
Conclusion
There’s still a lot more to talk about when it comes to styling the Shadow DOM. Let’s take a break for today and pick it up again tomorrow. As always if you have questions hit me up on twitter or leave a comment. Thanks!