Web Components and You (part 3): Simplifying with Lit

Published 2024-03-06, About 5 minute read.

We've seen that if you use attributeChangedCallback after you've registered your attribute in your static observedAttributes you can have your web components react to changes in its attributes. It's just a lot of wiring up to do, and we're used to easy reactivity in modern frameworks. Also, who wants to create an attribute every time something is reactive? (No one. It's a bad idea to do it that way!)

Enter Lit JS

Lit is a super light framework that smooths out all the wrinkles in using web compoenents. Working with lit, it honestly makes you feel like you're working in a framework, and the nice thing is that it's all based on web components: the shadow dom, custom elements, attributes, everything.

The two things that make Lit really shine are (1) its reactivity system and (2) it's template. Take a look at this example. If you change the attribute of the custom element, the template will change the string. And we didn't have to write code to make that change happen, it just reacted!

You may notice that there's a render() method, kind of like in React, Preact, Solid, etc. This method always returns a Template, and to get a template you can use the html`` function. This funny function takes a template literal, and does some magic to basically allow you to declare templates the same way you could in JSX.

The beauty of all this is that it's all basically just javascript. And when I say "just javascript," I mean that there's no precompilation of templates, no JSX template transformer to turn JSX into objects- you'll notice in this example, I'm loading the lit library at runtime and it works without any build.

Reactivity is quite easy to use even if you don't want an attribute to go with it, for example:

Above, we skip the attribute because the state that we're dealing with for our toggle is completely internal in this example. Thus, we used { state: true }, which means no attribute is set up for us, but we get the perks of having the template react to this property changing.

Also, we show how to set a default property value. In vanilla JS land you do this by setting the value in the constructor. If you use typescript you can set a default property right on the class. But check out the details here if you want to understand those nuances.

Finally, this example also shows how you can bind a click handler to toggle our state. This is pretty much what you expect, you just need to know the syntax.

Reacting to changing attributes and attribute reflection

Since Lit allows you to either est up a property or set up internal state, there is a new case to consider when using attributes. What if you want an attribute that your component reacts to, and you want that attribute to update in the dom if the component changes that attribute?

You might think this is common, but it isn't always a given. In a text input, you can set the initial value by providing a value: <input type="text" value="initial string" />. What's interesting is that if you were to type in the input this value attribute doesn't change. You can try it out:

This is the concept of reflection. Sometimes internal changes to a component aren't "reflected" like in the value attribute for a text input. There are other attributes that are reflected, however, like the "open" attribute for a details element. You'll have to again inspect this element and check to see if "open" attribue gets toggled on and off in the DOM:

Here's a video of me checking the open attribute is being reflected after opening/closing the details element:

Lit can allow these attributes to be "reflected" back to the DOM from internal changes. This is one of the strengths of using Lit as well- you get a lot of this boilerplate state management handled for you. For example, here we create a text component we call "Text" that has an attribute "str". This acts as the initial value, but if text in changed, the actual attribute in the DOM is changed with it:

Reflect all the things, right?!

You might draw the conclusion that we should just reflect everything by default. This is probably not a great idea, though, because it takes a little bit of browser work to update the dom for every change.

This is probably reserved for when HTML needs to have a declarative API, like "open" for details. The same can be said for the "disabled" attribute with inputs, or the "open" attribute for a dialog element.

Lit, of course, has a nice discussion about this.

What's next?

We've talked about how to use Lit to render web component templates and handle reactive changes. Maybe we should go back to styles and chat a bit how to use those with Lit! That's coming up.

⬅️ Previous Post Next Post ➡️
Back to top