A run of Web Components news crossed my desk recently so I thought I’d group it up here.
To my mind, one of the best use cases for Web Components is pattern libraries. Instead of doing, say, <ul class="nav nav-tabs">
like you would do in Bootstrap or <div class="tabs">
like you would in Bulma, you would use a custom element, like <designsystem-tabs>
.
The new Shoelace library uses the sl
namespace for their components. It’s a whole pattern library entirely in Web Components. So the tabs there are <sl-tab-group>
elements.
Why is that good? Well, for one thing, it brings a component model to the party. That means, if you’re working on a component, it has a template and a stylesheet that are co-located. Peeking under the hood of Shoelace, you can see this is all based on Stencil.
Another reason it’s good is that it means components can (and they do) use the Shadow DOM. This offers a form of isolation that comes right from the web platform. For CSS folks like us, that means the styling for a tab in the tab component is done with a .tab
class (hey, wow, cool) but it is isolated in that component. Even with that generic of a name, I can’t accidentally mess with some other component on the page that uses that generic class, nor is some other outside CSS going to mess with the guts here. The Shadow DOM is a sort of wall of safety that prevents styles from leaking out or seeping in.
I just saw the FAST framework¹ too, which is also a set of components. It has tabs that are defined as <fast-tabs>
. That reminds me of another thing I like about the Web Components as a pattern library approach: if feels like it’s API-driven, even starting with the name of the component itself, which is literally what you use in the HTML. The attributes on that element can be entirely made up. It seems the emerging standard is that you don’t even have to data-*
prefix the attributes that you also make up to control the component. So, if I were to make a tabs component, it might be <chris-tabs active-tab="lunch" variation="rounded">
.
Perhaps the biggest player using Web Components for a pattern library is Ionic. Their tabs are <ion-tabs>
, and you can use them without involving any other framework (although they do support Angular, React, and Vue in addition to their own Stencil). Ionic has made lots of strides with this Web Components stuff, most recently supporting Shadow Parts. Here’s Brandy Carney explaining the encapsulation again:
Shadow DOM is useful for preventing styles from leaking out of components and unintentionally applying to other elements. For example, we assign a
.button
class to ourion-button
component. If an Ionic Framework user were to set the class.button
on one of their own elements, it would inherit the Ionic button styles in past versions of the framework. Sinceion-button
is now a Shadow Web Component, this is no longer a problem.However, due to this encapsulation, styles aren’t able to bleed into inner elements of a Shadow component either. This means that if a Shadow component renders elements inside of its shadow tree, a user isn’t able to target the inner element with their CSS.
The encapsulation is a good thing, but indeed it does make styling “harder” (on purpose). There is an important CSS concept to know: CSS custom properties penetrate the Shadow DOM. However, it was decided — and I think rightly so — that “variablizing” every single thing in a design system is not a smart way forward. Instead, they give each bit of HTML inside the Shadow DOM a part, like <div part="icon">
, which then gives us the ability to “reach in from the outside” with CSS, like custom-component::part(icon) { }
.
I think part-based styling hooks are mostly fine, and a smart way forward for pattern libraries like this, but I admit some part of it bugs me. The selectors don’t work how you’d expect. For example, you can’t conditionally select things. You also can’t select children or use the cascade. In other words, it’s just one-off, or like you’re reaching straight through a membrane with your hand. You can reach forward and either grab the thing or not, but you can’t do anything else at all.
Speaking of things that bug people, Andrea Giammarchi has a good point about the recent state of Web Components:
Every single library getting started, including mine, suggest we should import the library in order to define what [sic] supposed to be a “portable Custom Element”.
Google always suggests LitElement. Microsoft wants you to use FASTElement. Stencil has their own Component. hyperHTML has their own Component. Nobody is just using “raw” Web Components. It’s weird! What strikes me as the worst part about that is that Web Components are supposed to be this “native platform” thing meaning that we shouldn’t need to buy into some particular technology in order to use them. When we do, we’re just as locked to that as we would be if we just used React or whatever.
Andrea has some ideas in that article, including the use of some new and smaller library. I think what I’d like to see is a pattern library that just doesn’t use any library at all.
- FAST calls itself a “interface system,” then a “UI framework” in consecutive sentences on the homepage. Shoelaces calls itself a “library” but I’m calling it a “pattern library.” I find “design system” to be the most commonly used term to describe the concept, but often used more broadly than a specific technology. FAST uses that term in the code itself for the wrapper element that controls the theme. I’d say the terminology around all this stuff is far from settled.
Have you examined https://haxtheweb.org yet?
And places like BYU are using vanilla WCs >> http://webcomponents.byu.edu
This Fast framework is beautiful! Definitely worth a try…they did a great job with the website… Is it by Microsoft? How does it differ with the Fluent UI?
Yes, FAST is is built and maintained by a team at Microsoft working with Edge (Much of the new Edge browser’s UI is built with a version of FAST). That said, the main goal of FAST is to help any developer accelerate the adoption of modern web platform features such as Web Components. To that end, FAST is not tied to a specific design system or framework so it can be useful for as many developers as possible. More info will be available shortly on how FAST and Fluent UI relate but I think you will like it (Needless to say, we are partners).
PatternFly Elements, Red Hat’s open-source design system, is a vanilla web component system. We use custom properties for theming and a light-DOM-as-data approach that means components gracefully upgrade.
Wow, shadow-parts sound great!
Dang, I might have to explore Web Components again.
Have they solved how to do a CSS reset across the components? I think I imported a specific reset file, which most likely wasn’t good!
Using a library like LitElement is in no way like using React. It imposes nothing on the user of the component other then the web standard. LitElement delivers a performance improvement at rendering time as well as syntactic sugar at development time.
WebComponents may have their use case, but I hate to see them in UI frameworks. They go against the web design! They are difficult to debug and style and a pain to work with. They are a solution to yesterday’s problems and an unnecessary complication to already over-engineered stack.
Nice round up, thanks!
About the “Nobody is using raw web components” part: From what I understand, the spec(s) are meant as a ‘low-level’ api, intended to be used with some kind of abstraction on top of them. When writing a “raw” web component, you often times write A LOT of boilerplate code. Which is why I go for an abstraction like LitElement.
It’s true that you buy into some kind of vender lock-in. But that is only for that specific component. You can totally write your tabs in Stencil and your buttons in LitElement while using some IonSomethings next to them.
Of course you can build some widgets on a site in Vue and some other in React, but then you’ll get both runtimes. When you just use web components as your components, most of the libraries/frameworks do not ship with a large runtime.
tldr; for me, the upside of developer experience outweighs the downside of a little lock-in. While not hurting user experience because the framework overhead is so little
You don’t! You can use
<fast-tabs>
and<sl-tabs>
on the same page, no problem.Chris, great update on the web components community. I like your thoughts on components that don’t use a library and wonder if you’d seen https://modest-bhaskara-e8742f.netlify.app/ from @passle_ on Twitter. Not only does it eschew the JS library dependency, but it aims to do the same to the CSS library dependency and make it really easy to “bring your own styles”.
I hope you’ll check it out, but I am also interest in the idea that you share here around using a library end up with us “just as locked to that as we would be if we just used React or whatever.” If I chose
<FrameworkFancytTabs/>
to run in my application, I’d need to figure a way to run that framework in my larger application (maybe it was already there, maybe not). And, because framework based components are a dependency you add to the framework that enforced parent to the component you choose to leverage will always exist, thus we have lock-in. However, when addressing any of the custom elements you’ve listed here, e.g.<fancy-tabs></fancy-tabs>
, the library used to construct it is a dependency of that element, so when you take or leave that element, same with the library dependency. There are of course custom elements that surface an API that is just as bound to that library as a framework, but that’s a result of API design that can and should be considered code smell, not that choosing to rely on a library enforces lock-in.I do agree that within a component, meaning as the component developer, the libraries we choose can and do cause a form of lock-in, though luckily it’s smaller scale and, as you not in this article, encapsulated. In this way I do hope that a more capable renderer-like API can be agreed upon for the platform. Have you spent any time with https://docs.google.com/document/d/1UVtF1gM6XY-_NKm2_c045K803U3ZTjfyluLeToJlIUk/preview or https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md two really interesting proposals that would start to bring this sort of capability natively to the browser?
This is a great post but I did want to highlight something.
“Web Components are supposed to be this “native platform” thing meaning that we shouldn’t need to buy into some particular technology in order to use them.”
Web Components were designed as part of the Extensible Web movement and were not intending to replace libraries/framewoeks. The idea was to make primitives that libraries could build on top of so they could ship less code. It was always the intention that you would use a library with them.
In theory this doesn’t create lock-in because the interface for the components is standard DOM APIs (attributes, properties, events). This means you can use a shoelace component with an ion component and even though they’re built with different libraries, they can still communicate and coexist.
Whether the strategy of creating low-level primitives for libraries to build on top of will ultimately be successful remains to be seen. Maybe Web Components should have fancier abstractions so you don’t need a library (I genuinely don’t know) but the main reason they don’t is because at the time everyone designing the APIs knew that there would be a good chance we’d get the abstractions wrong. We just hadn’t seen enough folks building custom elements to know what additional niceties they’d need.
As an example, there was an attempt to standardize data binding (the proposal was called Model Driven Views or MDV) which was based on Angular.js two-way bindings. That proposal was scrapped and, as it happens, the world moved on from two way bindings to more of a unidirectional data flow pattern (React props/Redux).
Now that we’re a few years on I think some things are becoming more clear. Maybe if a library based on web components becomes a massive success (like, jQuery level or success) that would show us “the way” and those abstractions could be standardized.
I think the huge difference is that you can combine multiple of those Web Components.
you wanna use
ion-tabs
andfast-tabs
at the same page? sure not problems go aheadSo maybe not many people use
class MyElement extends HTMLElement
directly but that doesn’t really matter as long as the base classes are “extensions” ofHTMLElement
. And that is the case for all of them – LitElement, Ionic, Fast, …You can see it like some sugar on top of HTMLElement. It helps me to write Web Component in the way I prefer. And everyone can choose his own sugar. No hard feelings.
PS: it’s a huge difference to frameworks… as using an Angular tabs implementation in a React App is probably not advised
Great article Chris. We created Stencil (I’m CEO of Ionic) so have a few thoughts on the above.
re: Shadow DOM: I agree, it’s not ideal in terms of making it easy to style, but we’re very excited about shadow parts. One thing that is missing in the Shadow DOM discussion is that it enables you to create a “Public API” around styles that makes it easier for users to move to new versions of the components without major breaking changes. For example, as a user of Bootstrap in the past, I saw felt firsthand how problematic it was to have a styling system based on class selectors without any enforced isolation, because you’ll very often get stuck on specific versions.
As for the last paragraph on lock in, I think the lock-in semantics here are fundamentally different with a web component library/compiler like Stencil than a framework like React or Angular, especially from the consumer side (i.e. a user adopting a web component-built library).
Yes, the web component author has to choose to use those libraries, but the consumers are free to pick whatever framework or library they want to. This is a fundamental difference between Web Components and the classic framework-component approach. And Stencil takes this one more step by automatically generating native bindings for React, Angular, Vue (and any future popular framework), so consumers of those frameworks can use the components in a framework-native fashion.
In our experience, moving to targeting Web Components has been transformational. We’re seeing strong stability in our output target (since WCs are standardized), but now a much larger set of the web world can use our components. We hope others try this approach and also see the benefits.
Thank you so much for Stencil Max! I literally built my first successful business around it: https://spx.dev and used it exclusively to develop an application for my final bachelor project. I passed with a perfect score of course. :-)
Stencil for the win!
Yeah, like Max said, the end user of a custom element can use that element in any framework, regardless what custom element lib the element author chose to use. The end user can take the element and stick it into a React app, a Vue app, an Angular app, a Svelte app, or an old jQuery app.
There’s a high amount of interop in the way end users can use custom elements, and with a smaller footprint than non-custom-element libs and frameworks (see Rob Eisenberg’s comment) so it’s a win situation even if particular libs are in use.
Expanding Rob Dodson’s comment, the current custom elements provide a lower-level set of primitive APIs that we can confidently rely on and know they won’t go away (that’s the hope). Then what may happen is that over time the most common and useful patterns that custom element libs end up re-creating often can help guide the next add-ons to the Web APIs, and the new APIs will allow custom element authors to achieve more while using even less libraries.
There are discussions around new features like declarative custom elements:
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Declarative-Custom-Elements-Strawman.md (discussion also happens in that repo’s issues tab)
or data binding for
<template>
elements (f.e. imagine using Handlebars, or some syntax, for reactive and declarative updates to DOM). See examples and join the conversation here:https://discourse.wicg.io/t/extension-of-template/447
Once the libraries iron out which patterns are great and which aren’t, then the best patterns can be added to the web more confidently.
A good place to check for which patterns are winning in performance is over in Stefan Krause’s JS Frameworks Benchmark:
https://krausest.github.io/js-framework-benchmark/current.html
What the fastest frameworks there have in common is that they use fine-grained updating mechanisms, rather than virtual DOM diffing. You’ll see a variety of new libs that are faster than React, Vue, or Angular, while still offering a nice developer experience.
Libs are still pushing boundaries: it would be bad for the web (for example) if the web adopted a vdom approach only for the concept to lose traction.
In particular, Solid.js from that benchmark is very neat and fast. I took it and built a custom element system on top of it, and now it @lume/element is one of the simplest and fastest custom element systems available:
https://github.com/lume/element
I would love any feedback!
TLDR, yes libs make the art of making custom elements easier and custom element authors may want to choose one, but the interoperability that end users get is incredibly robust and prevents end users from being locked into any particular framework. In the future, Custom Elements may be even simpler to write even without any libs, and you can help make that happen by joining the discussions in the above GitHub or WICG forums.
Great article but a small correction on the footnote: The FAST site calls itself an “interface system” and says in the next paragraph that it “can be used with any modern UI Framework” (Angular, React, Vue, etc.) not that it is a UI Framework.
Also, I may have misunderstood but it is worth pointing out that you don’t need to use FAST Element to use FAST components. You may want to use FAST element if you were to build your own components. That said, you could use FAST components then compose them with native web component code, or with lit, or however you want. The main reason FAST does not use “strait” web components is that there is still a huge amount of boilerplate and redundant code that would be needed so common features are consolidated into the FAST element library. That said, I do agree that it would be great if the platform just offered these features so we would not need to have FAST Element at all.
When I speak about web components, one of the things I remind folks is that what we have in the browser was intentionally designed as a set of very low-level un-opinionated APIs. The goal was to have library and framework authors come along and create an opinionated convenience layer over top of the primitives. This is exactly what FAST, Lit, Stencil, etc. have all done.
It is important to note that this is not the same as the JavaScript framework approach. Web component libraries are interoperable by default. If you start with FAST and then find a cool component created with Lit, you can use them together. There’s no problem there. And due to the fact that we are all building upon web standards, we implement much less code, resulting in smaller libraries. In fact, you can even use 2-3 web component libraries together and end up with something that’s still smaller than many front-end frameworks. Here’s another way to think about it, the minified and gzipped size of JQuery is about 29kb. You can fit both FASTElement and LitElement into that same space together and have plenty of room to spare, maybe even for a 3rd WC library. Here’s another thing we found when we implemented FAST. Our entire FASTElement library, plus our adaptive design system layer, plus all of our v1 core components, was about the same size as React+React.DOM alone. When you think about the value you are getting per KB with web components, it’s typically much higher than non-web-component options.
There’s been a lot of churn in the JavaScript framework landscape in past years. With Web Components, we’re working together to create a more open and interoperable solution that will empower designers and developers to create better apps without having to pay the JS framework cost over and over again.
The web components from htmlelements.com and vaadin may be worth mentioning in a future similar article. They build web components even before the release of the v1 spec for custom elements.
Here is my attempt to create a namespace registry:
https://github.com/nuxodin/web-namespace-registry