Web Components: creare elementi riutilizzabili per applicazioni moderne
Scopri come utilizzare i Web Components per creare interfacce modulari e riutilizzabili. Una guida completa a questa tecnologia nativa del browser per costruire elementi personalizzati interoperabili.

Introduzione
La complessità crescente delle applicazioni web ha portato ad un'evoluzione costante degli strumenti e dei paradigmi di sviluppo. Negli ultimi anni, abbiamo assistito alla proliferazione di framework JavaScript che, seppur potenti, hanno creato ecosistemi spesso isolati e incompatibili tra loro. Questa frammentazione ha complicato la condivisione di componenti UI e ha aumentato il rischio di dipendere da tecnologie proprietarie che potrebbero diventare obsolete.
In risposta a questa sfida, il W3C ha standardizzato i Web Components: un insieme di tecnologie native del browser che permettono di creare elementi HTML personalizzati, riutilizzabili e interoperabili. Questa innovazione rappresenta un passo significativo verso un web più componibile, dove gli elementi dell'interfaccia possono essere condivisi indipendentemente dal framework o dalla libreria utilizzata.
In questo articolo, esploreremo cosa sono i Web Components, come si sono evoluti nel tempo, perché rappresentano un'opzione importante per lo sviluppo moderno, e come utilizzarli concretamente nei vostri progetti. Vedremo come questa tecnologia nativa offra un'alternativa interessante ai componenti framework-specific, permettendo di costruire interfacce modulari che resistono al passare del tempo e ai cambiamenti dell'ecosistema frontend.
Cosa sono i Web Components
I Web Components sono un insieme di tecnologie web standard che permettono di creare elementi HTML personalizzati, incapsulati e riutilizzabili. A differenza dei componenti creati con framework come React o Vue, i Web Components funzionano nativamente nei browser moderni, senza dipendenze esterne.
Le tecnologie fondamentali
I Web Components si basano su quattro tecnologie principali, anche se oggi ci si riferisce spesso a tre di esse come il nucleo di questa specifica:
- Custom Elements: API che permette di definire nuovi tipi di elementi HTML
- Shadow DOM: Meccanismo che consente di incapsulare markup e stili, isolandoli dal resto del documento
- HTML Templates: Elementi
<template>e<slot>che consentono di dichiarare frammenti di markup riutilizzabili - HTML Imports: Una modalità per importare documenti HTML (ormai deprecata e sostituita dai moduli ES)
Queste tecnologie lavorano insieme per creare componenti autonomi che possono essere utilizzati in qualsiasi applicazione web, indipendentemente dagli strumenti di sviluppo scelti.
Caratteristiche principali
I Web Components offrono diverse caratteristiche chiave:
- Encapsulation: Lo Shadow DOM isola il CSS e il markup interno, evitando conflitti con il resto della pagina
- Riutilizzabilità: Una volta definiti, possono essere utilizzati in qualsiasi pagina o applicazione
- Interoperabilità: Funzionano con JavaScript vanilla e con la maggior parte dei framework
- Standardizzazione: Essendo specifiche W3C, sono supportati nativamente dai browser moderni
- Longevità: Sono meno soggetti all'obsolescenza rispetto ai componenti framework-specific
Concetti fondamentali
Per comprendere i Web Components, è importante familiarizzare con alcuni concetti chiave:
- Custom Element: Un elemento HTML personalizzato con comportamenti definiti tramite JavaScript
- Shadow DOM: Un DOM separato e incapsulato all'interno di un elemento
- Shadow Root: Il nodo radice dello Shadow DOM
- Shadow Boundary: Il confine che isola lo Shadow DOM dal DOM principale
- Slot: Punti di inserimento che permettono di passare contenuto dall'esterno all'interno di un componente
La storia dei Web Components
L'evoluzione dello standard
I Web Components hanno seguito un percorso di sviluppo relativamente lungo:
- 2011: Google introduce il concetto di Web Components al Google I/O
- 2012-2014: Sviluppo delle specifiche iniziali presso il W3C
- 2014-2015: Primo supporto parziale nei browser, principalmente Chrome
- 2016: Specifiche v0 per Custom Elements e Shadow DOM
- 2017-2018: Specifiche v1, più mature e con un consenso più ampio
- 2018-2019: Adozione crescente delle specifiche v1 da parte dei principali browser
- 2019-2020: Firefox completa il supporto per le API fondamentali
- 2020-2022: Crescita dell'ecosistema, con librerie, strumenti e componenti condivisi
- 2023-oggi: Adozione mainstream, con grandi aziende che costruiscono design system basati su Web Components
Dal polyfill al supporto nativo
Nei primi anni, i Web Components erano accessibili solo tramite polyfill come Polymer o X-Tag. Oggi, il supporto è ampiamente disponibile nei browser moderni, rendendo i polyfill sempre meno necessari.
L'impatto di framework e librerie
L'evoluzione dei Web Components è stata influenzata anche dall'ecosistema circostante:
- Polymer: Progetto Google che ha promosso e semplificato l'uso dei Web Components
- Stencil: Compilatore di componenti creato dal team Ionic
- Lit: Libreria leggera per la creazione di Web Components (evoluzione di Polymer)
- Framework ibridi: Svelte, Solid e altri che possono compilare in Web Components
Come funzionano i Web Components
Creazione di un Custom Element
Il primo passo per creare un Web Component è definire una classe che estende HTMLElement e registrarla come Custom Element:
// Definizione della classe per il componente
class HelloWorld extends HTMLElement {
constructor() {
super();
// Inizializzazione del componente
}
// Metodi del ciclo di vita
connectedCallback() {
this.innerHTML = `<h1>Hello, World!</h1>`;
}
}
// Registrazione del custom element
customElements.define("hello-world", HelloWorld);
Una volta registrato, l'elemento può essere utilizzato nel HTML come qualsiasi altro tag:
<hello-world></hello-world>
Utilizzo dello Shadow DOM
Per incapsulare il markup e gli stili, è possibile attivare lo Shadow DOM:
class EncapsulatedComponent extends HTMLElement {
constructor() {
super();
// Attiva lo Shadow DOM
this.attachShadow({ mode: "open" });
// Aggiunge contenuto allo Shadow DOM
this.shadowRoot.innerHTML = `
<style>
/* Questi stili sono limitati al componente */
p { color: red; }
</style>
<p>Questo paragrafo ha stile incapsulato</p>
`;
}
}
customElements.define("encapsulated-component", EncapsulatedComponent);
Lo stile definito all'interno dello Shadow DOM influenzerà solo gli elementi all'interno del componente, non il resto della pagina.
Template e slot
Con <template> e <slot>, è possibile creare componenti più flessibili:
<!-- Definizione del template -->
<template id="user-card-template">
<style>
.card {
border: 1px solid #ccc;
padding: 10px;
}
.name {
font-weight: bold;
}
</style>
<div class="card">
<div class="name">
<slot name="username">Nome utente predefinito</slot>
</div>
<div>
<slot name="details">Nessun dettaglio disponibile</slot>
</div>
</div>
</template>
class UserCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
// Clona il template e lo aggiunge allo Shadow DOM
const template = document.getElementById("user-card-template");
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define("user-card", UserCard);
Utilizzo con slot:
<user-card>
<span slot="username">Mario Rossi</span>
<span slot="details">Sviluppatore Web</span>
</user-card>
Ciclo di vita di un Web Component
I Custom Elements forniscono metodi del ciclo di vita che permettono di controllare il comportamento del componente:
- constructor(): Chiamato quando l'elemento viene creato o aggiornato
- connectedCallback(): Invocato quando l'elemento viene aggiunto al DOM
- disconnectedCallback(): Invocato quando l'elemento viene rimosso dal DOM
- attributeChangedCallback(name, oldValue, newValue): Chiamato quando un attributo osservato cambia
- adoptedCallback(): Chiamato quando l'elemento viene spostato in un nuovo documento
Per osservare gli attributi, è necessario definire un metodo statico observedAttributes:
class ResponsiveElement extends HTMLElement {
static get observedAttributes() {
return ["size"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "size") {
this.style.fontSize = `${newValue}px`;
}
}
}
Esempi pratici di Web Components
Vediamo alcuni esempi concreti di Web Components per casi d'uso comuni.
Esempio 1: Toggle Switch
class ToggleSwitch extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
.switch {
position: relative;
width: 60px;
height: 34px;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
:host([checked]) .slider {
background-color: #2196F3;
}
:host([checked]) .slider:before {
transform: translateX(26px);
}
</style>
<label class="switch">
<div class="slider"></div>
</label>
`;
this.addEventListener("click", this._onClick.bind(this));
}
_onClick() {
this.checked = !this.checked;
this.dispatchEvent(
new CustomEvent("change", {
detail: { checked: this.checked },
bubbles: true,
})
);
}
get checked() {
return this.hasAttribute("checked");
}
set checked(value) {
if (value) {
this.setAttribute("checked", "");
} else {
this.removeAttribute("checked");
}
}
}
customElements.define("toggle-switch", ToggleSwitch);
Utilizzo:
<toggle-switch id="myToggle"></toggle-switch>
<script>
document.getElementById("myToggle").addEventListener("change", (e) => {
console.log("Stato toggle:", e.detail.checked);
});
</script>
Esempio 2: Tooltip personalizzato
class CustomTooltip extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
:host {
position: relative;
display: inline-block;
}
.tooltip-content {
visibility: hidden;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background-color: black;
color: white;
padding: 5px 10px;
border-radius: 4px;
opacity: 0;
transition: opacity 0.3s;
}
:host(:hover) .tooltip-content {
visibility: visible;
opacity: 1;
}
</style>
<slot></slot>
<div class="tooltip-content">
<slot name="content">Tooltip default</slot>
</div>
`;
}
}
customElements.define("custom-tooltip", CustomTooltip);
Utilizzo:
<custom-tooltip>
Passa il mouse qui
<span slot="content">Questo è un tooltip personalizzato!</span>
</custom-tooltip>
Esempio 3: Card componente con immagine e contenuto
class ContentCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
margin: 16px;
max-width: 300px;
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-content {
padding: 16px;
}
::slotted(h2) {
margin-top: 0;
color: #333;
}
</style>
<img class="card-image">
<div class="card-content">
<slot></slot>
</div>
`;
// Accedi all'elemento immagine per impostarne l'src
this._imageElement = this.shadowRoot.querySelector(".card-image");
}
static get observedAttributes() {
return ["image"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "image" && this._imageElement) {
this._imageElement.src = newValue;
}
}
}
customElements.define("content-card", ContentCard);
Utilizzo:
<content-card image="https://esempio.com/immagine.jpg">
<h2>Titolo della card</h2>
<p>Descrizione della card con dettagli sul contenuto.</p>
</content-card>
Vantaggi dei Web Components
Vantaggi tecnici
- Standard nativi: Funzionano direttamente nei browser moderni senza dipendenze esterne
- Incapsulamento reale: Lo Shadow DOM isola veramente CSS e markup, risolvendo problemi di conflitto di stili
- Interoperabilità: Funzionano con qualsiasi framework o anche con JavaScript vanilla
- Performance: Essendo nativi del browser, possono offrire migliori performance rispetto a soluzioni di framework
- Progressive Enhancement: Permettono un approccio di miglioramento progressivo
Vantaggi di sviluppo
- Componenti riutilizzabili: Creabili una volta e utilizzabili in diversi progetti
- Componenti trasportabili: Non legati a un framework specifico, possono spostarsi tra progetti diversi
- Longevità: Meno soggetti all'obsolescenza dei framework
- Interfaccia DOM familiare: Utilizzano le API DOM standard
- Modularità: Promuovono uno sviluppo modulare e componibile
Vantaggi organizzativi
- Design System coerenti: Ideali per costruire librerie di componenti unificate
- Separazione delle competenze: Team diversi possono sviluppare componenti indipendenti
- Evoluzione graduale: Possibilità di modernizzare applicazioni esistenti in modo incrementale
- Proprietà intellettuale: Migliore protezione per componenti proprietari grazie all'incapsulamento
Quando usare i Web Components
Casi d'uso ideali
- Design Systems: Creazione di librerie di componenti coerenti per l'intera organizzazione
- Micro-frontend: Integrazione di porzioni di UI sviluppate da team diversi
- Widget embedded: Componenti destinati a essere inseriti in siti di terze parti
- Applicazioni di lunga durata: Sistemi che devono resistere ai cambiamenti di framework e tendenze
- Applicazioni ibride: Progetti che utilizzano diversi framework o stanno migrando da uno all'altro
Quando considerare altre soluzioni
Come ogni tecnologia, i Web Components hanno i loro limiti:
- Applicazioni small-team: Per piccoli team che lavorano su un'unica applicazione, un framework può offrire più produttività iniziale
- Ecosistema specifico: Se si è fortemente investito in un ecosistema (React, Vue, ecc.), potrebbe essere più efficiente rimanere all'interno di esso
- Funzionalità avanzate: Per alcune funzionalità molto avanzate di UI, i framework offrono soluzioni più pronte all'uso
Integrazione con framework e librerie
I Web Components possono essere utilizzati con la maggior parte dei framework moderni.
React
React può lavorare con Web Components, sebbene con alcune considerazioni:
// Utilizzo di un Web Component in React
function MyReactComponent() {
const handleToggleChange = (e) => {
console.log("Toggle cambiato:", e.detail.checked);
};
return (
<div>
<h2>Componente React con Web Component</h2>
<toggle-switch
onchange={handleToggleChange}
ref={(el) => {
// Accesso diretto al Web Component
if (el) el.checked = true;
}}
/>
</div>
);
}
Vue
Vue ha un'eccellente interoperabilità con i Web Components:
<template>
<div>
<h2>Componente Vue con Web Component</h2>
<toggle-switch :checked="isChecked" @change="handleChange"></toggle-switch>
</div>
</template>
<script>
export default {
data() {
return {
isChecked: false,
};
},
methods: {
handleChange(e) {
this.isChecked = e.detail.checked;
},
},
};
</script>
Angular
Angular supporta nativamente l'utilizzo di Web Components:
@Component({
selector: "app-component",
template: `
<h2>Componente Angular con Web Component</h2>
<toggle-switch
[checked]="isChecked"
(change)="handleChange($event)"></toggle-switch>
`,
})
export class AppComponent {
isChecked = false;
handleChange(event: CustomEvent) {
this.isChecked = event.detail.checked;
}
}
Svelte
Svelte può compilare direttamente in Web Components:
<svelte:options tag="svelte-counter" />
<script>
export let count = 0;
function increment() {
count++;
dispatch('change', { count });
}
function dispatch(name, detail) {
this.dispatchEvent(new CustomEvent(name, { detail }));
}
</script>
<button on:click={increment}>
Count: {count}
</button>
Strumenti e librerie per Web Components
Sebbene i Web Components possano essere scritti in JavaScript vanilla, esistono diversi strumenti che ne semplificano lo sviluppo.
Lit
Lit è una libreria leggera sviluppata da Google per semplificare la creazione di Web Components:
import { LitElement, html, css } from "lit";
class SimpleGreeting extends LitElement {
static properties = {
name: { type: String },
};
static styles = css`
p {
color: blue;
}
`;
constructor() {
super();
this.name = "Mondo";
}
render() {
return html`<p>Ciao, ${this.name}!</p>`;
}
}
customElements.define("simple-greeting", SimpleGreeting);
Stencil
Stencil è un compilatore che genera Web Components, creato dal team di Ionic:
import { Component, Prop, h } from "@stencil/core";
@Component({
tag: "my-component",
styleUrl: "my-component.css",
shadow: true,
})
export class MyComponent {
@Prop() first: string;
@Prop() last: string;
render() {
return (
<div>
Hello, {this.first} {this.last}
</div>
);
}
}
Altre librerie e strumenti
- Hybrids: Approccio funzionale alla creazione di Web Components
- Shoelace: Raccolta di componenti UI pronti all'uso
- Lion: Libreria di componenti a basso livello
- FAST: Sistema di design a Web Components di Microsoft
- Material Web Components: Implementazione di Material Design
- WebComponentsJS: Polyfill per browser più datati
La community e l'ecosistema
Crescita e adozione
Negli ultimi anni, l'adozione dei Web Components ha visto una crescita significativa:
- Grandi aziende: Google, Microsoft, Adobe, IBM, tra le altre, hanno investito in Web Components
- Open source: Crescenti contributi e progetti comunitari
- Performance: Miglioramenti continui nelle performance dei browser
- Design Systems: Sempre più sistemi di design basati su Web Components
- Formazione: Aumento dei contenuti educativi sull'argomento
Sfide rimanenti
Nonostante i progressi, alcune sfide persistono:
- Frammentazione: Diversi approcci per lo sviluppo (vanilla, Lit, Stencil, ecc.)
- Server-side rendering: Ancora complesso rispetto ad alcuni framework
- Accessibilità: Non garantita automaticamente, richiede attenzione
- Gestione stato: Meno sofisticata rispetto a framework come Redux o MobX
- Tooling: Strumenti di sviluppo meno maturi rispetto ad ecosistemi come React
Confronto con altre tecnologie
Web Components vs Framework Components
| Aspetto | Web Components | Framework Components |
|---|---|---|
| Standardizzazione | Standard web ufficiali | Specifici per framework |
| Interoperabilità | Elevata, funzionano ovunque | Limitata al loro ecosistema |
| Incapsulamento | Reale, grazie allo Shadow DOM | Simulato, attraverso convenzioni |
| Curva di apprendimento | Media, basata su API standard | Varia per framework |
| Ecosistema | In crescita, ma meno maturo | Vasto per framework popolari |
| Gestione stato | Basilare, richiede soluzioni aggiuntive | Spesso integrata |
| SSR | Possibile ma complesso | Più maturo in molti framework |
Web Components vs HTMX
| Aspetto | Web Components | HTMX |
|---|---|---|
| Focus | Componenti riutilizzabili | Interattività server-driven |
| Paradigma | Componenti client-side | Hypermedia server-side |
| Complessità client | Maggiore | Minore |
| Dipendenza dal server | Bassa | Alta |
| Casi d'uso ideali | Design systems, widget | Applicazioni CRUD, form |
Il futuro dei Web Components
Tendenze emergenti
- Declarative Shadow DOM: Permette il server-side rendering dei Web Components
- Form Associated Custom Elements: Migliore integrazione con i form HTML
- Progressive Enhancement: Uso crescente per migliorare i componenti HTML esistenti
- CSS Scope: Alternative complementari allo Shadow DOM con meno restrizioni
- Islands Architecture: Web Components come isole di interattività in pagine principalmente statiche
Evoluzioni delle specifiche
Le specifiche Web Components continuano a evolversi:
- Constructable Stylesheets: Per stili più efficienti e condivisibili
- ElementInternals: API per personalizzare il comportamento interno
- ShadowRoot adoptedStyleSheets: Per condividere stili tra componenti
- CSS Shadow Parts: Per stilizzare componenti dall'esterno
- Container Queries: Per componenti realmente auto-contenuti e responsive
Adozione crescente
Con il miglioramento delle specifiche e degli strumenti, si prevede una crescente adozione in:
- Design Systems aziendali: Per garantire coerenza attraverso prodotti diversi
- Integrazioni multi-framework: Per facilitare la collaborazione tra team con stack diversi
- Componenti di terze parti: Per widget embeddable e integrabili
- Micro-frontend: Per architetture modulari con team indipendenti
Conclusione
I Web Components rappresentano un passo importante verso un web più componibile, dove elementi dell'interfaccia possono essere creati una volta e riutilizzati ovunque. La loro natura standard e interoperabile offre un'alternativa convincente all'approccio framework-specific, specialmente per componenti che devono essere condivisi tra progetti diversi o resistere al passare del tempo.
La maturazione delle specifiche, il miglioramento del supporto browser e la crescita dell'ecosistema di strumenti hanno reso i Web Components una scelta sempre più praticabile per lo sviluppo di applicazioni moderne. La loro capacità di incapsulare markup, stili e comportamento in un singolo pacchetto autonomo risponde a una necessità fondamentale nello sviluppo frontend contemporaneo.
Come ogni tecnologia, i Web Components non sono la soluzione universale a tutti i problemi. Il loro utilizzo ottimale richiede una valutazione attenta del contesto specifico: dimensione del progetto, requisiti di longevità, necessità di interoperabilità e competenze del team. In molti casi, possono coesistere efficacemente con framework e librerie, offrendo il meglio di entrambi i mondi.
Che si tratti di sviluppare un design system aziendale, migrare gradualmente un'applicazione legacy, o creare componenti destinati a essere incorporati in diversi contesti, i Web Components meritano sicuramente considerazione come parte del toolkit moderno dello sviluppatore frontend. La loro standardizzazione, unita alla crescente adozione da parte di aziende e community, suggerisce che continueranno a giocare un ruolo importante nel futuro dell'architettura web.
Risorse utili
- Specifiche Web Components - MDN
- Web Components - web.dev
- Lit - Libreria per creare Web Components
- Stencil - Compilatore di Web Components
- open-wc - Raccomandazioni e strumenti per Web Components
- WebComponents.org - Repository di componenti e risorse
- Awesome Web Components - Collezione di risorse
- Custom Elements Everywhere - Test di compatibilità con framework