Lit Web Components
Lit Web Components
Lit
Lit
is a lightweight JavaScript
library for building reusable Web Components
. The best place to test it is at the Lit Playground
.
An example structure of a LitElement
is very similar to that of a regular custom element in JavaScript
, but a fair amount of the heavy-lifting is done by Lit
, such as parsing
, diffing
, and DOM
updates. Additionally Lit
handles automatically adding a “shadow DOM
” to each element, which would have been manually achieved via element.attachShadow({ mode: 'open' })
.
Just as with a regular custom element it is essential to add the line customElements.define('custom-element', CustomElement)
after defining the class, as this is what is used to ensure the HTML
recognises the element. As such it is important that the script is imported before the custom tag is used in the DOM
. This issue is slightly different if your site tags are primarily generated via JavaScript
as the .define
method will fire when the module is imported to the currently running script, but importing the script doesn’t actually do much other than ensure the element is usable, as you don’t need the import in order to use document.createElement('custom-element')
, but without it you will run into errors in the HTML
. It is worth noting that JavaScript
has a “smart” import system and won’t redefine custom elements every time that you import them, so it is best practice to add the import line to any connected components, and to the main script.
A Note on Web Components
As per the mdn web docs
: “Web Components
is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilise them in your web apps”
In essence it is a design pattern for making reusable code for HTML
/ CSS
/ JavaScript
. There’s no exact science to a Web Component
, you could use HTML
templates via <template>
and <slot>
, as well as a “shadow DOM
”. It is similar to writing .jsx
files, except that here you are using vanilla JavaScript
to encapsulate HTML
/ CSS
in string form within JavaScript
.
A Note on Shadow DOM
s
A “shadow DOM
” (sDOM
for later reference) is essentially a DOM
tree that is separate from the main DOM
. This sDOM
is internal to the element and styling from CSS
does not leak in or out, such that the element is only affected by its internal styling defined within its sDOM
.
A sDOM
can be “open” or “closed” (attachShadow({ mode: 'open' })
or attachShadow({ mode: 'closed' })
). If the sDOM
is “open” then it is accessible via JavaScript
.
Normally browser dev tools will still show sDom
elements.
A Note on ESM
(ECMAScript
Modules)
The official JavaScript
module system, standardised in ES6
in 2015. Allow use of the import
/export
syntax between different files. This can be done via CDN
(Content Delivery Network
) with something similar to import { html } from 'https://cdn.jsdelivr.net/npm/lit@3.3.0/+esm'
, or locally with something similar to import { html } from './node_modules/lit/index.js'
[!NOTE] The syntax for directly importing, as above, is not to be confused with the syntax used in files where build tools are used. In that case you’d see something like
import { html } from 'lit'
, and an associatedpackage.json
file
1 2 3 import { html } from './lit.js'; // Relative file path => No build tools import { html } from 'https://...'; // URL => No build tools import { html } from 'lit'; // Module name => REQUIRES build tools
Example Lit
Components
There are some examples of Lit
components below, from the official Lit
playground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Original at https://lit.dev/playground/#sample=examples/hello-world
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/npm/lit@3.3.0/+esm';
export class SimpleGreeting extends LitElement {
static styles = css`p { color: blue }`;
static properties = {
name: {type: String},
};
constructor() {
super();
this.name = 'Somebody';
}
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
customElements.define('simple-greeting', SimpleGreeting);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Original at https://lit.dev/playground/#sample=examples/full-component
import {LitElement, html, css} from 'lit';
export class MyElement extends LitElement {
static properties = {
greeting: {},
planet: {},
};
// Styles are scoped to this element: they won't conflict with styles
// on the main page or in other components. Styling API can be exposed
// via CSS custom properties.
static styles = css`
:host {
display: inline-block;
padding: 10px;
background: lightgray;
}
.planet {
color: var(--planet-color, blue);
}
`;
constructor() {
super();
// Define reactive properties--updating a reactive property causes
// the component to update.
this.greeting = 'Hello';
this.planet = 'World';
}
// The render() method is called any time reactive properties change.
// Return HTML in a string template literal tagged with the `html`
// tag function to describe the component's internal DOM.
// Expressions can set attribute values, property values, event handlers,
// and child nodes/text.
render() {
return html`
<span @click=${this.togglePlanet}
>${this.greeting}
<span class="planet">${this.planet}</span>
</span>
`;
}
// Event handlers can update the state of @properties on the element
// instance, causing it to re-render
togglePlanet() {
this.planet = this.planet === 'World' ? 'Mars' : 'World';
}
}
customElements.define('my-element', MyElement);
[!NOTE] Similarly to creating custom elements in vanilla
JavaScript
, the constructor cannot take arguments
A Note on JavaScript tagFunction
The documentation for the tagFunction
can be found here. They are primarily used alongside template literals
in JavaScript
. A template literal
is a string within backticks. The most common use I find is similar to Python
’s f-string
which has the syntax f"Hello, {name}"
, with the JavaScript
syntax for the same being `Hello, ${name}`
.
A tagFunction
is a special function that processes a template literal
. When you write a “tagged template” like html`Hello, ${name}`
, you are using the tagFunction
(html
) on the template literal
. The tagFunction
(html
) receives the raw string parts and the values of the embedded expressions separately, allowing it to customise how the final output is constructed. In the case of Lit
it is enabling features like safe HTML
rendering / parsing. It creates a template object that Lit
can efficiently update and render to the DOM.
A Note on diffing
The properties
attribute of the custom element class above is a list of “reactive” properties to listen for / observe. When one of these properties changes, the render()
method is called to update and re-render only the parts of the element that changed. This selective approach to updating elements in the DOM
is part of diffing
, where two files are compared to see where they differ.
This is a case of single element diffing
, Lit
sees a reactive property (“reactive prop”) has changed and calls the render()
method to get the new html template from html
, which is a tagFunction
, as in the line return html`...`
. With this new html template Lit
will “diff” it against the previous template and update only the specific parts of the element that changed, not all elements within it.
This is a fine-tuned / lightweight single element approach to diffing
and is contrasted by the approach taken by React
and similar, which use a virtual DOM
and perform diffing
on that full document, only updating the parts of the real DOM
where changes are found.
Miscellaneous
- For testing purposes you can get by with the
CDN
versionimport { LitElement, html, css} from 'https://cdn.jsdelivr.net/npm/lit@3.3.0/+esm'
, otherwise you can install vianpm
and use a build tool - At the time of writing the current version is
Lit v3.3.0
, you can find theCDN
version here - Tested on
Google Chrome Version 137.0.7151.104 (Official Build) (64-bit)
- You can find a “starter kit” here but the actual repository layout is quite bloated for a simple learning project