The Pros and Cons of Web Components, Via Lit and Shoelace
While developers enjoy working with components in framework libraries, web components are gaining more interest because they work with HTML and CSS and reduce the need for JavaScript. But they also offer the ability to write custom components, enabling larger internal software estates to keep more control of the look and feel on their pages. After our recent story on Shoelace (soon to be re-named Web Awesome) I thought I would take that library for a spin.
Before we look at Shoelace, let’s take a quick look at the level just below it, the Google web component library called Lit.
A Quick Look at Lit
This gives us an idea of how components are constructed. We just want to pick out the basic bits, because this is what Shoelace is built on. We’ll just look at the code through the playground here.
All we want to do is make a rating button, which takes a thumbs up (and goes green) or thumbs down (red) and changes the rating accordingly.
You can see that we pull in the JavaScript index.js as a module and use our own defined tag called rating-element
. The span defined in style
doesn’t affect the component because of the isolation of the shadow DOM.
Let’s extract the interesting bits from the code:
You can see the import of Lit, and the definition of the RatingElement class extending a LitElement. At the bottom of the file, you can see the registration of the tag as a custom element based on RatingElement:
1 |
customElements.define('rating-element', RatingElement); |
There is a render method that basically builds the basic element:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
render() { return html` <button class="thumb_down" @click=${() => {this.vote = 'down'}}> <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="..."/></svg> </button> <span class="rating">${this.rating}</span> <button class="thumb_up" @click=${() => {this.vote = 'up'}}> <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="..."/></svg> </button>`; } |
So, that is quite a bit of code to do something quite simple, but you do get your own reusable component.
Shoelace
Let’s go one layer up and use some Shoelace. Now we get built components.
We will install a Shoelace template that uses the rollup bundler and start from there. The bundler helps to resolve components without lazy loading them from the web. This brings us closer to a standard developer workflow.
First I clone the rollup example template. That will have the right npm packages we need:
Then we install the packages. You may well need to do an npm update
too.
And finally, run the project:
And kick up the page on a different shell tab:
This is what you should see:
So how did we get these components to show?
First of all, we state in index.js which components we want to load in the bundle:
1 2 3 4 5 6 7 8 9 10 11 |
import '@shoelace-style/shoelace/dist/themes/light.css'; import '@shoelace-style/shoelace/dist/themes/dark.css'; import SlButton from '@shoelace-style/shoelace/dist/components/button/button.js'; import SlIcon from '@shoelace-style/shoelace/dist/components/icon/icon.js'; import SlInput from '@shoelace-style/shoelace/dist/components/input/input.js'; import SlRating from '@shoelace-style/shoelace/dist/components/rating/rating.js'; import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js'; // Set the base path to the folder you copied Shoelace's assets to setBasePath('/dist/shoelace'); // <sl-button>, <sl-icon>, <sl-input>, and <sl-rating> are ready to use!% |
So that is where the Shoelace button, input and rating components come from. This leaves the index.html to be very lean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!doctype html> <html> <head> <title>Shoelace Rollup Example</title> <link rel="stylesheet" href="dist/bundle.css"> </head> <body> <h1>Shoelace Rollup Example</h1> <sl-button type="primary">Click me</sl-button> <br><br> <sl-input placeholder="Enter some text" style="max-width: 300px;"></sl-input> <br><br> <sl-rating></sl-rating> <script src="dist/index.js"></script> </body> </html> |
Note that the index.js the HTML refers to is the one unrolled by rollup and placed in the distribution directory.
Want a dark theme? Just alter the index.html to:
1 |
<html class="sl-theme-dark"> |
And because we already imported the dark theme in the index.js:
Finally a little bit of interactivity (don’t forget to refresh your cache between bigger changes).
Let’s add a toast-style alert (one that goes to the corner) to the button and give the toast a duration countdown before switching off.
We include the alert component to index.js:
1 2 3 4 5 6 |
... import SlIcon from '@shoelace-style/shoelace/dist/components/icon/icon.js'; import SlInput from '@shoelace-style/shoelace/dist/components/input/input.js'; import SlRating from '@shoelace-style/shoelace/dist/components/rating/rating.js'; import SlAlert from '@shoelace-style/shoelace/dist/components/alert/alert.js'; ... |
We place the component in our index.html, replacing the button code:
1 2 3 4 5 6 7 |
<div class="alert-duration"> <sl-button variant="primary">Show Alert</sl-button> <sl-alert variant="primary" duration="3000" countdown="rtl" closable> <sl-icon slot="icon" name="info-circle"></sl-icon> This alert will automatically hide itself after three seconds, unless you interact with it. </sl-alert> </div> |
And some control code back in the index.js, before the end:
1 2 3 4 |
const container = document.querySelector('.alert-duration'); const button = container.querySelector('sl-button'); const alert = container.querySelector('sl-alert'); button.addEventListener('click', () => alert.toast()); |
And the result is already quite impressive:
(What you can’t see is the blue countdown line shrinking at the bottom of the alert)
Conclusion
This is just an introduction to using web components with a library like Shoelace — they need a bit of attention initially, but (like a framework) have a lot of rich content. However, unlike a framework, these are working mainly with HTML and CSS.
To make things easier for React users to transition, every Shoelace component can be available to import as a React component. The downside is that SSR (server-side rendering) is still not suitable with web components. And it is true that custom elements are not quite the same as components; the problems that this might cause are fleshed out here.
But overall, if you are thinking of working in or leading a larger web implementation team, make sure you understand the possible benefits of a web component library.