This article isn’t about how to build web components. Caleb Williams already wrote a comprehensive guide about that recently. Let’s talk about how to work with them, what to consider when making them, and how to embrace them in your projects.
If you are new to web components, Caleb’s guide is a great read, but here are more resources that will get you up to speed:
- Web Components — the "right" way
- Shadow DOM v1: Self-Contained Web Components
- Web Components on MDN
- Awesome Web Components
- Open Web Component Recommendations
Since web components are now widely supported (thanks to the hard work of many people behind the scenes) — and considering the imminent switch that Edge will make to the chromium platform — people are now thinking about web components as "native" and a platform-compliant way to build reusable UI components to keep consistency across design systems and web projects, while using the power of the Shadow DOM to encapsulate style and logics inside the component itself.
Well, this can be true and false at the same time. But first, let’s get acquainted with the Abstraction Layers Triangle.
Technically, we should consider web components as an extension of our favorite markup language, HTML (yep!). The Web Components API allows us to create custom HTML elements (e.g.
<foo-bar>) that don’t exist in HTML.
Frameworks and libraries like React, Vue, Angular, SvelteJS work on their abstraction level, right above other tools that live in a sort of "middle earth," like StencilJs, Hybrids and Lit. Under these layers of abstraction, we can find our basic web technologies… and vanilla web components. We can represent this concept with an ALT (Abstraction Layers Triangle) diagram:
Why is this important? Well, it helps us visualise the various layers that exist on top of native components and understand the context they are used so that they can be built for an intended context. What's context? That's where we're headed.
::theme pseudo-elements, which is something Monica Dinculescu) has covered so well.
Before designing components, we need to know how they will be used, so determining the type of context is a key to all of this. The most easy way is targeting only one context, but with a small CSS trick. we can build our components for both of them.
Let’s look at the differences between the two contexts before we get into that.
A private context is a closed ecosystem that provides components with their own style that must be used as-is. So, if we are building a component library that comes from specific styling guidelines or a design system, each component will reflect custom styles so there’s no need to code them up each time they’re needed.
querySelector. As a a result, we can simply pick and use all any component, avoiding issues like style inconsistencies and CSS collisions. As the author, you can also get to define a set of CSS custom properties (or
::parts) that can be used to style a component for a specific use case, but this is not the focus point of a design system.
Here’s an example of a web component component in a private context. It has all of the styles and logic contained inside its
shadow-root and and can simply be dropped into any page.
This example and the one to follow are for demonstration purposes and not intended for production use because they do not make considerations for key situations, like accessibility and other optimisations.
Components in a private context can be rarely used outside of that context. For example, if we try to take an element from a design system (which has its own enforced styles), we’re unable to simply add it to a project and expect to customise it. You know how Bootstrap can be themed and customised to your liking? This is pretty much the opposite of that. These components are made to live inside their context and their context only.
A standard context may be the most complex type of component, not only because the environment can range anywhere from a full-fledged framework (like Vue and React) to plain vanilla HTML, but also because everyone should be able to use that component like any other element.
For example, when adding a component publicly, say to npm, those who use it will expect to be able to customise it, at least to some degree.
Do you know of any HTML element that comes with its own presentational style? The answer should be no because, well, elements must be explicitly styled with CSS. This is also true for web components made in a standard context. A single web component should be customisable by adding classes an attributes, or other methods.
Here’s the same
<todo-list> element that we saw in the closed context example, but designed for a standard context. It works as-is, without any presentational styles inside its shadow root. In fact, it only contains the required logic and baseline CSS to make sure it functions. Otherwise, it’s completely customisable like any standard HTML element, like a div.
Both of the examples we’ve looked at for each context are made with the same component. The difference is that the component in a standard context an be customised and selected with external CSS.
OK, so working with web components is really the same as working with plain HTML, though as we’ve seen, it’s important to follow the paradigms and principles of the given content. Well, thing we need to be mindful of is the web component composition.
As explained by Google Web Fundamentals:
Composition is one of the least understood features of shadow DOM, but it's arguably the most important.
In our world of web development, composition is how we construct apps, declaratively out of HTML. Different building blocks (
<input>s) come together to form apps. Some of these tags even work with each other. Composition is why native elements like
<video>are so flexible. Each of those tags accepts certain HTML as children and does something special with them. For example,
<select>knows how to render
<optgroup>into dropdown and multi-select widgets. The
<summary>as an expandable arrow. Even
<video>knows how to deal with certain children:
<source>elements don't get rendered, but they do affect the video's behavior. What magic!
Composition is what we normally do when we work with HTML. Since web components are merely HTML elements with a DOM reference — and not logical containers — we should rely on composition to build our components and any sub-components. If you think about the
ul and and
select you will notice that you declaratively compose these elements to get the final output and you have specific attributes to be used with the main component (e.g.
[readonly]) or the sub-component (e.g.
[selected]). This is true also for web components, and if you are building a custom list, consider to build the main component (
<custom-list>) and the child one (
<custom-li>). Using the
[slot] element, you can define where children elements will be placed and also placeholder content that will be shown when no children are passed through.
Another thing to consider is that "small" topic we call accessibility. Since we are creating completely new HTML elements, we need to consider the accessibility of our elements in order to provide a basic semantic role, any keyboard navigation as well as the user’s operating system preferences, like the reduce motion and high contrast settings.
I strongly suggest the following resources as reference for building accessible and inclusive components, how to define semantic markup, and how to implement a basic keyboard navigation.
- Inclusive Components
- Accessible to All on web.dev
- WAI-ARIA Authoring Practices
- WebAIM WCAG Checklist
Web components are an emerging technology in web development and, as such, there really aren’t any clearly defined best practices to guide us as far as building them for their intended or maximized use. When you find yourself working with them, perhaps the single thing you can take away from this post is to consider whether they are intended for a closed context or a standard context, then ask yourself WHI:
- Who will use this component?
- How much flexibility should that person have to customize it?
- Is this component for everyone or for a specific audience?