I built a Web components library!
Fredrik BergqvistA couple of months ago I built an app for keeping track of Star Wars Unlimited game state, using web components without a framework. (Read more here: Star Wars Unlimited Base Counter).
I was pretty happy with the result, but I fell into the trap (?) of using the shadow DOM in all components. This made styling really cumbersome, and for no real reason. Rendering updates in components was also something I should revisit in the future, as I'm not happy with the current state in the base counter app.
In december last year I started migrating this very site from Next.js to Eleventy, and as I mostly started using .md files, I really found a use case for small web components providing functionality when needed.
The modal component
The first component I made was a modal, which is used in the information section in the footer of this page. Again I started out with a version using shadow DOM and templates, but after some thinking I refactored it to use light DOM only. I want the components I make to be as easy to style as possible, and only really provide functionality with some default styling.
“Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn.”
Legrasse had one point in advance of Professor Webb, for several among his mongrel prisoners had repeated to him what older celebrants had told them the words meant. This text, as given, ran something like this:
“In his house at R’lyeh dead Cthulhu waits dreaming.”
<button type="button" class="btn btn-border" onclick="document.querySelector('.my-example-modal').showModal()">Click to open Modal</button>
<nidhugg-modal class="my-example-modal" heading="The Call of Cthulhu">
<p><i>“Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn.”</i></p>
<p>Legrasse had one point in advance of Professor Webb, for several among his mongrel prisoners had repeated to him what older celebrants had told them the words meant. This text, as given, ran something like this:</p>
<p>“In his house at R’lyeh dead Cthulhu waits dreaming.”</p>
</nidhugg-modal>
How does it work?
The first iteration made use of the template system for inserted content, which makes it easy to slot in content where you want it. I created slots for heading, main content and footer, which should suit most needs for a modal.
When I refactored it to use light DOM, could not really keep the same structure, as I want the close button to always be present. Instead, I made the (optional) heading into a property of the component, and the content is placed directly inside the component tags.
With this approach I still have to insert the content into my own dialog markup, which is done when the component is added to the document which triggers the connectedCallback()
-function.
In a perfect world, I would probably rather be able to use template with light DOM, but the world isn't perfect, is it?
Disadvantages of light DOM: FOUC when the component is loaded
One thing I noticed when using light DOM is that the content is visible before the component is loaded, causing a flash of unstyled content and will also cause a layout shift on the page.
Luckily there is a CSS pseudo-class :defined
that can be used to hide the component until it is fully loaded.
nidhugg-modal:not(:defined) {
display: none;
}
The Image modal component
The next component I built was the image modal; I needed a way to show images in full size without leaving the page.
This component was a lot more straightforward to build, as it only needed to show an image in a modal. I used the same approach as with the modal component but with a few tweaks.
As I wanted to support multiple images in the same component, I made the decision to put any configuration options on the image element itself, using data-
attributes. This way I can add multiple images to the component and style them differently if needed.
When the image is clicked, I copy the attributes from the image to the modal, and show the modal with the image inside.
The modal is also only inserted once in the document, so if you have multiple images on the page, they will all use the same modal element.
See more examples and how to install it here.
Back to top
The last component I built was a back-to-top button, which is a simple button that scrolls the page to the top when clicked. You should be able to see it in the bottom right corner of the page.
It allowed me to try out the new scroll animation API, which allows for animating elements based on scroll position.
The fallback uses intersection observer to show the button when the user scrolls down a certain amount, and hides it when the user scrolls back up.
See how to use it here.
Conclusion
I'm really happy with the components I've built so far, and since I am a proponent of the more basic web, where HTML and CSS can take more of the load over JavaScript frameworks. I already have a project at work which uses Go and HTML, and web components will be a perfect fit for that project.
There are a few more components that I want to build, and I will add them to the nidhu.gg site as I go along.