They're here to stay. π€
<my-component>
<video>
<video controls>
<source
src="....."
type="video/webm"
/>
</video>
<details>
Hello from details
<details>
<summary>Native browser elements</summary>
<p>Hello from details</p>
</details>
<audio>
β similar to video but for audio (with built-in
controls)
<dialog>
β native modal / dialog element with built-in
APIs
<canvas>
β drawing surface with isolated rendering context
<meter>
, <progress>
β built-in semantics
for progress / measurement
Ecc..
A modern library for building fast, lightweight, and reusable Web Components.
Lit embraces a minimalist philosophy: it builds on the web platform, adds only what's necessary, and stays out of your way for everything else.
(β’Μα΄β’Μ )Ω
import { html, LitElement } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("my-badge")
export class MyBadge extends LitElement {
render() {
return html`<slot></slot> `;
}
}
//...
@customElement("my-badge")
export class MyBadge extends LitElement {
static styles = css`
:host {
display: inline-block; padding: 0.25em 0.4em;
font-weight: 700; text-align: center;
border-radius: 0.25rem; color: #fff;
background-color: #0d6efd;
}
`;
render() {
return html`<slot></slot> `;
}
}
<my-badge>Badge</my-badge>
This decorator allows the component to provide a configurable API, which can be set or updated by the component's users.
Properties can be fine-tuned with options
type
: conversion (String, Number, Boolean, etc.)
reflect
: sync value to attributeattribute
: custom attribute name (or false
)
converter
: custom logic for attribute β property
This decorator creates a private reactive field that exists only inside the component, and yet every time its value changes, Lit will still trigger a re-render.
//...
export class MyBadge extends LitElement {
@property({ type: String }) appearance = "";
static styles = css`
:host {
//other styles with default values and background...
}
:host([appearance="secondary"]) {
background-color: #6c757d;
}
`;
render() {
return html`<slot></slot> `;
}
}
<my-badge appearance="secondary">Badge</my-badge>
//...
export class MyBadge extends LitElement {
@property({ type: String }) appearance = "";
static styles = css`
//other styles with default values and background...
slot {
color: #fff;
}
`;
render() {
return html`<slot></slot> `;
}
}
<my-badge style="color: #000">Badge</my-badge>
HTMLElement
and follow its lifecycle. π
connectedCallback
is called when the element is attached to
the DOM.
disconnectedCallback
is called when the element is detached from
the DOM.
attributeChangedCallback
is called when an attribute is changed.
willUpdate(changedProperties)
is called before each update,
with the changed properties
updated(changedProperties)
is called after each update, with
the changed properties
render()
defines the component's template and returns HTML.
firstUpdated(changedProperties)
is called after the first render.
Handling events in Lit is straightforward. β¨
Bind events directly in the template using the custom @
syntax.
//... export class MyButton extends LitElement {
#handleClick() { console.log("Button clicked!"); }
render() { return html` <button @click=${this.#handleClick}> Click me! </button> `; } }
Encapsulation β Isolation: some events can bubble outside the shadow boundary.
click
, input
, change
can be listened outside the shadow root.
mouseenter
, mouseleave
,
focus
, blur
do not escape the shadow root.
Directives are special functions in Lit that can customize how template parts are rendered.
They are a powerful tool for conditional rendering, looping, async data fetching, and creating reusable template logic
β§ο½‘Ω©(ΛαΛ )Ωβ§*q
π repeat
π§© when
βοΈ map
//... import { repeat } from 'lit/directives/repeat.js';
export class MyList extends LitElement { items = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
render() { return html` <ul> ${repeat( this.items, (item) => item.id //<-- Key function for efficient updates (item) => html`<li>${item.name}</li>` )} </ul> `; }
}
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="./my-badge.js"></script>
</head>
<body>
<my-badge appearance="secondary">Badge</my-badge>
</body>
</html>
Agular has its own component model and template compiler
By default, Angular only recognizes known Angular components
...Angular thinks it's just an unknown HTML tag
(α΅βα΄β)
We need to teach Angular that custom elements are valid
Angular provides a special schema:
CUSTOM_ELEMENTS_SCHEMA
With this schema, Angular stops complaining π
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-root',
template: `<my-badge appearance="secondary">Badge</my-badge>`,
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppComponent {}
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Angular App</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<script src="https://cdn.example.com/my-lit-library.js"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
import '@my-org/my-lit-library/my-lit-library.js';
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
custom-elements.json
It's a metadata file that describes your Web Components.
A standard way to document Web Components π
It includes details like:
//install library npm install --save-dev @custom-elements-manifest/analyzer
//add script to package.json "analyze": "cem analyze --litelement",
//then, run the script npm run analyze
And now we finally have custom-elements.json π
custom-element-vs-code-integration
//install library npm install --save-dev custom-element-vs-code-integration
//create script custom-vscode.js import { generateVsCodeCustomElementData } from "custom-element-vs-code-integration"; import manifest from "./path/to/custom-elements.json"; const options = {...}; generateVsCodeCustomElementData(manifest, options);
//modify script to package.json "analyze": "cem analyze --litelement && node custom-vscode.js",
//then, run the script analyze agaiin npm run analyze
π Native adoption of Web Components keeps growing
π§© More tooling & integrations will simplify cross-framework use
π Companies are already moving to framework-agnostic libraries
@customElement("q-and-a")
class QandA extends LitElement {
render() {
return html`<p>Waiting for your questions... πββοΈ</p>`;
}
}