Shadow DOM: incapsulamento

Shadow DOM: incapsulamento

Shadow DOM è la tecnologia che garantisce vero incapsulamento ai Web Components: stili e markup interni al componente sono isolati dal documento principale, eliminando i conflitti CSS e proteggendo l'integrità del componente. È uno standard nativo del browser, supportato senza polyfill nei browser moderni. Vediamo come funziona e quando usarlo.

Cos'è Shadow DOM

Shadow DOM crea un'albero DOM secondario attaccato a un'elemento host, completamente isolato dal DOM principale. CSS dichiarato dentro lo shadow root non esce, e CSS esterno non entra (salvo eccezioni come CSS custom properties). Il browser tratta lo shadow come un'unità atomica: gli eventi attraversano il bordo con event.composed, ma per il resto è una sandbox visiva e strutturale completa.

Modalità open vs closed

attachShadow({mode: 'open'}) permette di accedere allo shadow tramite element.shadowRoot. Modalità closed nega questo accesso esterno: il riferimento è solo interno alla classe. Open è la scelta più comune per consentire ispezione e debug, closed è usata raramente quando vuoi nascondere completamente l'implementazione interna del componente al codice esterno per ragioni di sicurezza o disciplina di accesso.

CSS custom properties per personalizzazione

Lo shadow boundary blocca CSS esterno, ma CSS custom properties (variabili --) attraversano il confine. È il meccanismo raccomandato per personalizzare componenti dall'esterno: il componente espone API tipo --my-button-color, --my-button-padding e l'utente del componente le imposta dal parent. Esempio: my-button { --my-button-color: blue; }. Il componente legge dentro lo shadow con color: var(--my-button-color, black). Pattern standard nei design system moderni basati su Web Components per offrire customization controllata e prevedibile.

Part attribute per stilizzazione esterna

Per casi avanzati di personalizzazione, l'attributo part dentro lo shadow espone elementi al CSS esterno tramite il pseudo-selettore ::part(). Esempio: <button part="cta"> dentro lo shadow, stilizzabile con my-component::part(cta) { ... } dal parent. Più granulare delle custom properties ma anche più accoppiato. Usalo con parsimonia per evitare di esporre dettagli implementativi che vorresti poi cambiare. Combinazione custom properties + part offre flessibilità completa pur mantenendo encapsulation di base e gestione disciplinata dei punti di estensione esposti.

Eventi attraverso lo shadow

Gli eventi attraversano lo shadow boundary in modo controllato. Per default un'evento dispatchato dentro lo shadow ha target = element host visto dall'esterno (re-targeting), preservando incapsulamento. Composed:true permette all'evento di propagarsi fuori dallo shadow attraverso ancestor chain. Eventi nativi (click, keydown, etc.) hanno composed=true di default. Eventi custom devono dichiarare esplicitamente composed:true per attraversare. Comprensione necessaria per debug: se un listener nel light DOM non riceve un'evento dispatchato nello shadow, controlla composed.

Slotted content e light DOM

Il contenuto child di un Custom Element rimane nel light DOM (visibile a CSS esterno, accessibile via querySelector standard). Lo slot proietta questo contenuto in posizioni specifiche dentro lo shadow. Esempio: <my-card><h1>Titolo</h1></my-card> dove my-card ha <slot> nello shadow. L'h1 è light DOM (CSS della pagina lo stila), ma renderizzato dentro la card. CSS interno può stilare lo slotted con ::slotted(h1). Pattern composto che combina vantaggi: light DOM accessibile + shadow DOM strutturato + customization via slot.

Strumenti e debugging

Chrome DevTools mostra Shadow DOM nell'Elements tab con badge "#shadow-root". Espandi per vedere il sottoalbero, ispeziona stili applicati. Lit DevTools extension aggiunge insights specifici per componenti Lit con reactivity. Storybook supporta Shadow DOM componenti, utili per design system. Per testing, librerie helper come @open-wc/testing offrono utility per query elementi nello shadow. Constructable Stylesheets (adoptedStyleSheets) sono ottimizzazione moderna: condividono CSS tra istanze senza duplicare nel DOM. Supporto ottimo nei browser moderni. Per componenti riusati molte volte sulla pagina, riduce memoria e migliora performance del primo rendering significativamente.

Procedura passo-passo

  1. In un Custom Element chiama this.attachShadow({mode: 'open'}) nel constructor.
  2. Imposta il markup interno: this.shadowRoot.innerHTML = '<style>...</style>...'.
  3. Definisci stili che applicheranno solo al componente.
  4. Usa <slot> per ricevere contenuto dal light DOM esterno.
  5. Per slot named: <slot name="header"> e <span slot="header"> nel light.
  6. Stila slot stesso con ::slotted(selettore) dal CSS interno.
  7. Per CSS custom properties: l'esterno definisce --color, l'interno lo usa.

Errori comuni e come risolverli

  • Aspettarsi CSS globale: stili esterni non penetrano (per design); usa custom properties per personalizzazione.
  • Eventi non visti dall'esterno: usa composed:true nel CustomEvent per attraversare lo shadow boundary.
  • ::slotted selettore complesso: ::slotted accetta solo selettori compound, non discendenti.
  • document.querySelector: non vede dentro lo shadow; usa element.shadowRoot.querySelector.
  • Closed mode senza necessità: limita debugging e accesso da test; usa open per default.

Domande frequenti

D: Shadow DOM rallenta la pagina?
R: Praticamente no; è ottimizzato nativamente. Migliora persino il rendering grazie all'isolamento.

D: Funziona con SEO?
R: Sì, il contenuto è accessibile ai crawler moderni. Per SSR serve approccio specifico.

D: Posso accedere dal parent al shadow content?
R: Solo in open mode tramite element.shadowRoot. È buona pratica esporre API dedicate.

D: Shadow DOM va sempre usato nei Web Components?
R: No, è opzionale. Senza Shadow DOM hai un Custom Element senza incapsulamento di stili.

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?