Hi, do you ever find yourself working on a web app and wish you had an HTML tag that does something not in the HTML spec? You've also been working on this site for a while, so the HTML tag needs to work across all your previous decisions: plain HTML, Markdown, Angular, React, and some Svelte and Vue and Ember and other choices.
It should work everywhere an HTML tag like a div or heading one works. Well, did you know you can make your own custom HTML tags?
By the end of this video, we'll have made our own custom HTML tag, word-viewer, that when given a list of words, we'll show them one at a time, and clicking it will toggle it running forwards and backwards. My definition of 'word' is pretty loose, though.
Then we'll use this HTML tag in all the frameworks.
Hi, I'm Andrew Jakubowicz, a developer on the Google Lit team and welcome to the very first video in our Build it with Lit series, the video series where we build stuff using Lit. This video is a fast overview of using various Lit concepts to build something concrete and then use it. For those who want to go deeper into each concept or get hands-on with the code, check out the video description. For each chapter of this video, there is a playground link to what we've built so far and links to documentation for concepts covered.
One last thing before we make our awesome component.
What is Lit?
What is Lit?
Lit is an absolutely tiny library that removes the boilerplate of defining a web component. This is done by extending LitElement, a class that provides reactivity and familiar life cycle methods.
And it's a very light abstraction. LitElement extends the browser's own HTMLElement. lit.dev says it best: every Lit component is a native web component with the superpower of interoperability. Web components work anywhere you use HTML, with any framework or none at all. This makes it ideal for building shareable components, design systems, or maintainable, future-ready sites and applications.
Lit is used in some incredible components available in open source. For example, the model-viewer component, like an image tag but for 3d models. Or the brick-viewer component, like the model viewer but for brick models. Or this chessboard element. Or Lit playground; it's open source and you can use it. We'll be doing all our Lit coding inside a Lit Playground. Wild.
Building the Component Shell
What does a Lit component look like?
The skeleton of a simple component can be seen on the lit.dev home page in both TypeScript and JavaScript. Our custom component is a new class that extends LitElement. The HTML that is rendered goes in a render method. Finally, we tell the browser the name of our HTML tag with either the @customElement decorator in TypeScript, or the browser's customElements.define function. When the browser encounters the simple-greeting HTML tag, it finds your component definition and runs your code.
Let's write our very first component. I'll use TypeScript as it's ever so slightly terser and I like the types. I'll also use decorators, which are not yet standard. Lit uses these as an optional way to provide declarative APIs for things like registering elements and reactive properties. JavaScript examples on lit.dev show alternatives to decorators.
Import html and LitElement from Lit and the customElement decorator. Without .js the import will not work. Define our WordViewer class extending LitElement. Add a render method returning an HTML tagged template literal.
We interrupt this broadcast to demystify this html tag. There is no string concatenation happening here. Instead, this html tag followed by backticks is called a tagged template literal. Basically, this html tag caches everything and won't do any work unless your dynamic properties change. More info linked in the description.
Finally, tell the browser the HTML tag name for your component. And this is everything required for the component to work on the TypeScript side. Execute the code on the HTML page, and now the browser knows what a word-viewer is, an HTML tag you invented.
Adding Reactive Properties
We want to pass words into our word-viewer—words that will be viewed. Lit's @property decorator does this, setting up an HTML attribute for us and creating a reactive property. Import the @property decorator. We'll store the passed-in words in a property called words. Let's render this property straight out to see that it is working.
The property decorator creates an HTML attribute for us with a matching name. So we can pass in our words.
While we're here, let me prove to you that the component will react to property changes. The browser understands these components, so we can use the browser's debugging features right here. Let's try some out. We can change the words and the component updates. We can also directly set words from the developer console. Although you may have noticed the HTML attribute is now out of date. If you really want the HTML attribute to reflect the property, there's an option we can pass into our property decorator, in this case, reflect, which indicates that we want the property to be reflected on the HTML tag attribute. And now when we change the property in the console, it reflects. Neat.
This was an example and not needed, so I'm going to delete the reflect option. While we have the developer console open, you may see these comments with Lit mentioned. Lit adds these to identify the dynamic parts of the DOM. Then Lit can efficiently replace this dynamic content when properties change.
Implementing State and Lifecycle Methods
We can pass in our words but have no way to automatically change them over time yet. At a high level, we will use setInterval to repeatedly call a function that cycles through the words. We setInterval when the component is attached to the page and stop cycling with clearInterval when the component is removed or disconnected. And we know when the component is added or removed by using the component's lifecycle methods. We have them all listed on lit.dev. We are interested in the standard custom element lifecycle callbacks: connectedCallback for setup and disconnectedCallback for cleanup.
Time to code. We need a new class property called index that will store the index of which word we are looking at. Because it's going to be private internal state, we use the @state decorator. It's the same as @property but doesn't create an HTML attribute. Then in the render method, we'll split the words and show just a single word by index.
We need a method bound to the instance to increment this index, which will change the displayed word. And then in the connectedCallback and disconnectedCallback lifecycle methods, we can start an interval and clean up an interval.
Note, I've not added a @property or @state decorator on the interval timer. Making something reactive just means it will automatically schedule an efficient render on change, but storing or changing the ID to the running timer never has an impact on the visuals so it doesn't need reactivity. Now this super simple implementation works great.
Styling and Handling Events
We're almost ready to use it everywhere. It just needs the click event to toggle play direction and some styles because it's looking a little default. A very easy and quick way to add styles is with the static styles property. Import a css tag and now we can add CSS as you'd expect. :host refers to styling the component itself. This makes the word viewer violet and add some padding. We'll go much deeper into styles in a future video.
We can toggle the playback by adding a method and binding it to a click. Lit makes this super easy. playDirection state keeps track of which way we are playing. We'll use this to update the word we're displaying. Now the index can go negative so we need to fix up the math to stay positive. Calling switchPlayDirection switches play direction, and the arrow function binds the method to the instance.
Lit automatically manages event listening with an @ prefix, so @click calls switchPlayDirection when clicked. To make the play direction more visually obvious, we'll use a classMap directive to change the class based on the play direction. This tiny directive efficiently manages applying and removing classes. We can now easily change the background color by playing the backwards class when we're playing in reverse. The CSS is what you may expect. Elements with the backwards class will be violet. Cool.
Using Your Component Everywhere
That's all the core stuff working. Let's use this component everywhere. Using this component is as simple as dropping a script tag on the page and then writing your HTML tag when you want to use it. After all, you've invented a real HTML tag, so you can use it like one. Because this is a quick video, we're going to use no tooling at all to go really fast. Warning: don't do this in production.
Let's copy out the JavaScript from the playground network tab and replace the Lit import with a CDN import. This small bundle of JavaScript tells the browser about our web component. We can use CodePen to find some examples of various technologies. Here's a React example. Ideally, we'd have another script tag, but for speed, we'll just dump the JavaScript below the React code. Now we can add our word viewer right in the JSX. And it works.
Okay, what about Vue? Let's find a Vue starter. Paste in our JavaScript. And now we can use our component in Vue. Nice. I personally like to write my content using Markdown, but love to add interactive custom bits. Hey, Lit works.
Again, let's find a Markdown editor. Once again, make sure our component is defined. This is how I love writing content.
Conclusion and Next Steps
Thank you so much for watching. Leave a comment if there is something you'd like to see or if you have any questions, and join us on Slack or tweet at us. The Lit & Friends workspace has thousands of users discussing Lit, web components, and related topics. Links on the screen and lots of links in the description for everything we've covered. Next time we'll build on these concepts to pull off a simple carousel. Can't wait to see you then.