Web Components and You (part 4): Styles
Published 2024-03-11, About 6 minute read.
We have options when we want to include CSS in our web components. Each solution has differing levels of support by different browsers, levels of ease of use, and levels of reusability. Here's a few I'd like to share:
- Inline styles in the shadow root
- Using Lit's
css`` helper
- Adopting stylesheets
- Using tailwind two ways
The easiest: inline styles 📎
Let's face it, web components really champion component encapsulation and they really enforce this using style scoping with the shadow DOM. Styles outside of the shadow DOM don't leak into the shadow DOM, so you can depend on the styling and structure inside of web component remaining intact no matter where you place it.
This method is really not my favorite way to do do styles... For the past 20 years we've been preached to about letting CSS, HTML, and JS focus on each of their concerns, so let CSS be CSS and keep it in the .css file. Well, now we're putting CSS in the HTML.
So here is a super simple example:
Here's some text!
(I'm not winning any design awards here 😅)
If you think about it, this is what a lot of frameworks do, though. In Vue and Svelte at least there is a section of the component file that are style tags, and it's understood that the component will encapsulate the styles to just that component. At least, in Vue, you can use a `scoped` attribute to make sure the styles stay specific to the component.
Here, though, it doesn't feel quite as neat. It looks like we're putting CSS inside of our HTML inside of our Javascript... which feels like we're literally mixing up concerns.
Partly, I think this is a problem that has yet to be completely solved, but there are other ways to skin this cat...
Using the Lit CSS helper 📎
By far one of the easiest ways to include CSS but still keep it somewhat separated from the template is to use Lit' css`` helper.
Here's the same example from above but using Lit:
Here's some text!
Things feel much better here! CSS has it's own place, HTML is separate and in a render function. It just feels better. If you want, you can define your css in a different file and import it.
This is all depending on Lit. So maybe that's fine, but maybe you want to stay more native?
Adopting a constructed stylesheet 📎
All modern browsers have the Constructable Style Sheet API. This allows you to create a style sheet object, parse styles, and add them to the object. Then you can "adopt" the style using the adoptedStyles property on the shadowRoot.
A few things have this adoptedStylesheet: the Document, shadow roots, and any document objects in an iframe. This is essentially the way Lit includes styles under the hood, but it's nice to be able to do this without a framework!
Here is how you could adopt a style:
Here's some text!
Not too bad, right? It's not as visually clean as Lit, but it's almost there. There's some ceremony you have to go through to create a sheet and adopt the style, and we probably could have made it more concise.
There is another way to do this that is really slick, but unfortunately Firefox and Safari don't support it! 😭 It's still nice to see it in action. It involves a sweet new thing call import assertions!
Here's an example of native css modules being imported into javascript and adopted!