Introduzione
I Web Components sono una delle tendenze più interessanti nello sviluppo front-end moderno perché permettono di creare componenti HTML incapsulati, riutilizzabili e indipendenti dal framework usato. In pratica, puoi costruire un elemento personalizzato una sola volta e poi riutilizzarlo in più pagine o progetti, mantenendo comportamento e struttura coerenti.
In questo tutorial vedremo un caso d’uso molto pratico: un menu a schede riutilizzabile, utile per sezioni di impostazioni, pannelli informativi, dashboard o aree di contenuto organizzate per categorie. L’obiettivo non è solo creare un componente funzionante, ma capire come progettare un Web Component pulito, accessibile e facile da integrare in un progetto reale.
Questo esempio è particolarmente utile perché un menu a schede richiede una logica chiara: pulsanti per cambiare vista, contenuti separati, stato attivo, e comportamento prevedibile. Tutto questo si presta benissimo a essere incapsulato in un componente personalizzato.
Codice completo
<!-- Utilizzo del componente nella pagina -->
<tab-menu>
<button slot="tab" data-target="profilo">Profilo</button>
<button slot="tab" data-target="sicurezza">Sicurezza</button>
<button slot="tab" data-target="preferenze">Preferenze</button>
<div slot="panel" data-name="profilo">
<h3>Profilo</h3>
<p>Qui puoi aggiornare nome, email e immagine profilo.</p>
</div>
<div slot="panel" data-name="sicurezza">
<h3>Sicurezza</h3>
<p>Gestisci password, autenticazione a due fattori e dispositivi collegati.</p>
</div>
<div slot="panel" data-name="preferenze">
<h3>Preferenze</h3>
<p>Configura lingua, tema e notifiche dell´applicazione.</p>
</div>
</tab-menu>
<script>
class TabMenu extends HTMLElement {
constructor() {
super();
// Incapsulamento dell´interfaccia interna
this.attachShadow({ mode: ´open´ });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: system-ui, sans-serif;
border: 1px solid #d0d7de;
border-radius: 12px;
overflow: hidden;
background: #fff;
}
.tabs {
display: flex;
gap: 0;
border-bottom: 1px solid #d0d7de;
background: #f6f8fa;
}
.tabs button {
appearance: none;
border: 0;
background: transparent;
padding: 0.9rem 1rem;
cursor: pointer;
font: inherit;
color: #57606a;
border-bottom: 3px solid transparent;
}
.tabs button[aria-selected="true"] {
color: #0969da;
border-bottom-color: #0969da;
font-weight: 600;
background: #fff;
}
.panel {
display: none;
padding: 1rem;
}
.panel.active {
display: block;
}
</style>
<div class="tabs" role="tablist" aria-label="Menu a schede">
<slot name="tab"></slot>
</div>
<div class="content">
<slot name="panel"></slot>
</div>
`;
}
connectedCallback() {
// Attende che gli slot siano disponibili nel DOM
requestAnimationFrame(() => {
this.tabs = this.querySelectorAll(´[slot="tab"]´);
this.panels = this.querySelectorAll(´[slot="panel"]´);
this.tabs.forEach((tab, index) => {
tab.setAttribute(´role´, ´tab´);
tab.setAttribute(´aria-selected´, index === 0 ? ´true´ : ´false´);
tab.tabIndex = index === 0 ? 0 : -1;
tab.addEventListener(´click´, () => {
this.activateTab(tab.dataset.target);
});
});
this.panels.forEach((panel, index) => {
panel.classList.add(´panel´);
if (index === 0) panel.classList.add(´active´);
});
if (this.tabs[0]) {
this.activateTab(this.tabs[0].dataset.target);
}
});
}
activateTab(targetName) {
this.tabs.forEach((tab) => {
const isActive = tab.dataset.target === targetName;
tab.setAttribute(´aria-selected´, isActive ? ´true´ : ´false´);
tab.tabIndex = isActive ? 0 : -1;
});
this.panels.forEach((panel) => {
const isActive = panel.dataset.name === targetName;
panel.classList.toggle(´active´, isActive);
});
}
}
customElements.define(´tab-menu´, TabMenu);
</script> Spiegazione
Il componente personalizzato si chiama tab-menu. È definito estendendo la classe HTMLElement e registrato con customElements.define(). Questo significa che nel markup HTML possiamo usare direttamente il nuovo tag come se fosse un elemento nativo.
1. Struttura con slot
Il componente usa gli slot per ricevere contenuti esterni. Gli elementi con slot="tab" diventano i pulsanti delle schede, mentre quelli con slot="panel" rappresentano i contenuti associati. Questo approccio è molto utile perché separa la logica del componente dal contenuto che vuoi inserire.
2. Shadow DOM per l’incapsulamento
Con attachShadow({ mode: ´open´ }) creiamo uno Shadow DOM, cioè un albero DOM separato dal documento principale. Il vantaggio è importante: gli stili interni non “inquinano” il resto della pagina e, allo stesso tempo, gli stili globali del sito non rompono il componente.
3. Gestione dello stato attivo
La funzione activateTab(targetName) confronta il valore data-target dei pulsanti con il valore data-name dei pannelli. Quando trova corrispondenza, rende attivo il tab corretto e mostra il pannello giusto. Questo è un pattern semplice ma molto efficace: il componente non dipende da ID complessi o da logiche esterne, ma usa attributi data ben leggibili.
4. Accessibilità di base
Abbiamo aggiunto alcuni attributi ARIA come role="tablist", role="tab" e aria-selected. Anche se il componente può essere ulteriormente migliorato, questa base aiuta tecnologie assistive e lettori di schermo a interpretare meglio l’interfaccia.
Best practice
- Usa nomi chiari per gli attributi data:
data-targetedata-namerendono il codice leggibile e facile da mantenere. - Preferisci componenti piccoli e focalizzati: un Web Component dovrebbe fare bene una cosa sola. Un menu a schede è un ottimo esempio di responsabilità singola.
- Non dipendere da librerie esterne: uno dei punti forti dei Web Components è la portabilità. Più il componente è autonomo, più sarà riutilizzabile.
- Imposta uno stato iniziale coerente: il primo tab dovrebbe essere attivo di default, così l’interfaccia è subito comprensibile.
- Curare l’accessibilità: aggiungere ruoli, stati e comportamento da tastiera è fondamentale per un componente usabile da tutti.
- Evita logiche troppo rigide: se possibile, lascia che il contenuto dei tab sia definito dall’utente del componente. Questo aumenta la flessibilità.
- Stilizza con attenzione: lo Shadow DOM protegge dagli effetti collaterali, ma è bene progettare CSS semplici e prevedibili, con classi semantiche e selettori chiari.
Riepilogo
In questo tutorial abbiamo visto come creare un menu a schede riutilizzabile con HTML e Web Components. Il cuore della soluzione è composto da tre idee principali:
- Custom Element per definire un nuovo tag HTML;
- Shadow DOM per isolare struttura e stile;
- Slot per inserire contenuti personalizzati dall’esterno.
Questo approccio è moderno, modulare e adatto a progetti che vogliono ridurre dipendenze e duplicazione di codice. Inoltre, il pattern visto qui può essere adattato facilmente ad altri componenti: accordion, wizard, pannelli laterali, selettori di contenuto e molto altro.
Se vuoi portare il componente a un livello superiore, il passo successivo è aggiungere la navigazione da tastiera con frecce, il supporto a Enter e Space, e una gestione più robusta del focus. Sono miglioramenti che rendono il componente davvero pronto per l’uso in produzione.
Approfondisci con risorse ufficiali
- MDN Web Docs - Web Components: documentazione completa e aggiornata sui componenti personalizzati.
- MDN Web Docs - Custom Elements: guida pratica alla creazione di nuovi elementi HTML.
- MDN Web Docs - Shadow DOM: spiegazione dettagliata dell’incapsulamento del DOM.
- MDN Web Docs - HTML slot element: approfondimento su come funzionano gli slot nei Web Components.
- WHATWG HTML Standard: riferimento ufficiale per lo standard HTML e il comportamento degli elementi del web.
