Custom elements HTML5

Custom elements HTML5

I Custom Elements sono il cuore dei Web Components: permettono di registrare nuovi tag HTML con comportamento personalizzato, esponendo un'API standard che funziona in qualsiasi contesto. Da pulsanti riutilizzabili a widget complessi, i Custom Elements offrono encapsulation e riusabilità senza dipendere da framework. Vediamo come crearli e usarli in modo efficace.

Definizione di un Custom Element

Un Custom Element è una classe ES6 che estende HTMLElement (o un'altro elemento built-in per Customized Built-in). Il nome del tag deve contenere almeno un trattino (kebab-case) per evitare collisioni con tag HTML standard. Si registra tramite customElements.define('mio-tag', MiaClasse). Una volta registrato puoi usarlo come qualsiasi altro tag HTML, anche dinamicamente da innerHTML.

Lifecycle callbacks

I Custom Elements espongono callback automaticamente invocati dal browser: constructor (creazione istanza), connectedCallback (inserimento nel DOM), disconnectedCallback (rimozione dal DOM), attributeChangedCallback (cambio attributo osservato), adoptedCallback (spostamento a un'altro document). Per osservare attributi devi anche dichiarare get observedAttributes come array statico dei nomi degli attributi da monitorare per cambio.

Customized built-in elements

Oltre ai Custom Elements autonomi puoi estendere elementi HTML esistenti: class FancyButton extends HTMLButtonElement, registrato con customElements.define('fancy-button', FancyButton, {extends: 'button'}). Si usa come <button is="fancy-button">. Eredita comportamento del button base inclusi form submission, accessibility, keyboard. Limitazione: Safari non li supporta nativamente, richiede polyfill (@webcomponents/webcomponentsjs). Per cross-browser compatibility molti progetti preferiscono autonomous custom elements anche quando estendere sarebbe più naturale semanticamente.

Lazy registration

I Custom Elements supportano upgrade ritardato: puoi inserire <mio-tag> nel DOM prima ancora che la classe sia registrata. Quando customElements.define viene chiamato, tutti i tag già nel DOM vengono "upgraded" automaticamente. Questo permette di lazy-loadare il codice JavaScript del componente: l'HTML statico funziona come placeholder, lo script si carica via import() dinamico quando necessario, e il componente prende vita. Eccellente per performance: solo i componenti effettivamente usati pagano il loro cost di parsing.

Eventi custom dal componente

I Custom Elements possono dispatchare eventi custom per comunicare con il parent: this.dispatchEvent(new CustomEvent('myaction', {detail: {key: value}, bubbles: true, composed: true})). Composed:true permette all'evento di attraversare lo shadow boundary, raggiungendo listener nel light DOM. Pattern Pub/Sub naturale: il componente non sa chi ascolta, il parent decide quali eventi gestire. Standard EventTarget garantisce compatibilità completa con framework: React può aggiungere listener via ref, Vue via @myaction, Angular via (myaction). API unificato per integrazione cross-framework.

Form-associated custom elements

Custom Elements che vogliono partecipare nei form (compilazione, validation, FormData) implementano la Form-Associated API: static get formAssociated() { return true } e usano this.attachInternals() per ottenere ElementInternals. Tramite questa API il custom element accede al form parent, imposta value e validity, è incluso in FormData al submit. Sostituisce hack precedenti con input hidden sincronizzati. Esempio: un component custom-rating che esprime il suo valore al form come fosse un'input nativo. Standardizzato e supportato dai browser moderni nel 2026.

Patterns avanzati

Patterns avanzati per Custom Elements: state management interno con observer pattern, render efficiente con DocumentFragment, observed attributes per reactivity, dispatchEvent per comunicazione parent. Per state complesso, librerie integrabili come Lit Reactive Properties, MobX, Redux gestiscono complexity. Per styling, Constructable Stylesheets (adoptedStyleSheets) condividono CSS tra istanze multiple senza duplicazione. Per testing, Web Test Runner e Open WC offrono utility specifiche per Custom Elements. Per debugging, Chrome DevTools Elements tab mostra Custom Elements distintamente, e Lit Debugger DevTools extension aggiunge insights specifici per componenti basati su Lit.

Migrazione da framework

Migrare componenti React/Vue a Web Components: identifica componenti puramente UI senza dipendenze framework heavy, riscrivili come Custom Elements con Lit per ridurre boilerplate, esponi props come attributi/properties, eventi come CustomEvent. Test con storybook e visual regression per parità. Migrazione graduale: i Web Components convivono con i componenti React/Vue esistenti nello stesso progetto, sostituibili uno a uno. Approccio pragmatico che evita big-bang rewrite. Beneficio finale: design system riusabile in qualsiasi progetto futuro, indipendente dal framework che il team sceglierà domani.

Procedura passo-passo

  1. Crea una classe che estende HTMLElement: class CardWidget extends HTMLElement.
  2. Nel constructor chiama super() prima di tutto.
  3. Opzionalmente attacca shadow DOM: this.attachShadow({mode: 'open'}).
  4. Imposta il markup in connectedCallback per evitare problemi di inizializzazione.
  5. Definisci static get observedAttributes() restituendo array di nomi.
  6. Implementa attributeChangedCallback(name, oldValue, newValue) per reagire ai cambi.
  7. Registra: customElements.define('card-widget', CardWidget).

Errori comuni e come risolverli

  • Setup nel constructor: alcune operazioni DOM possono fallire prima di connessione; usa connectedCallback.
  • Tag in camelCase: i nomi HTML sono case-insensitive ma per convenzione usa kebab-case con almeno un trattino.
  • observedAttributes mancante: senza dichiarazione attributeChangedCallback non viene chiamato; dichiara sempre.
  • Eventi listener senza cleanup: in disconnectedCallback rimuovi listener per evitare memory leak.
  • Doppia registrazione: registrare lo stesso nome due volte lancia errore; usa customElements.get per check.

Domande frequenti

D: Posso estendere elementi esistenti come button?
R: Sì, Customized Built-in: class MyBtn extends HTMLButtonElement, con is="my-btn". Safari non li supporta nativamente.

D: Posso usare TypeScript?
R: Sì, perfettamente. Le definizioni di HTMLElement sono native.

D: Custom Elements sono lazy-loadable?
R: Sì, l'upgrade avviene quando la classe viene registrata, anche dopo l'inserimento del tag.

D: Funzionano in SSR?
R: Solo lato client per default. Per SSR usa Lit-SSR o framework con supporto nativo.

Hai bisogno di aiuto?

Se vuoi sviluppo web con il team di G Tech Group, scrivici tramite il modulo di contatto.

Hai trovato utile quest'articolo?