Shadow DOM è un confine di stile: le regole della pagina non vengono applicate e le regole dei componenti non vengono divulgate, ad eccezione delle proprietà ereditate (incluse le proprietà personalizzate). Superala deliberatamente attraverso i canali riportati di seguito.
:host, :host() e :host-context()
/* The component's own host element, styled from inside its shadow tree. */
:host { display: block; }
/* Only when the host matches a selector (e.g. a reflected state attribute). */
:host([disabled]) { opacity: 0.5; pointer-events: none; }
/* Based on an ancestor in the Light DOM; useful for theming. */
:host-context([theme="dark"]) { background: #111; color: #eee; }
Evita :host-context(). Raggiunge l'esterno del confine per testare gli elementi predecessori, ma Firefox e Safari hanno rifiutato di implementarlo e il CSSWG è propenso a non svilupparlo ulteriormente. Consideralo un vicolo cieco solo per Chromium, non un'opzione lungimirante. Utilizza invece una proprietà personalizzata CSS ereditata (vedi sotto): il consumer imposta la proprietà su un predecessore e il componente la legge oltre il confine, il che funziona ovunque e mantiene entrambi i lati disaccoppiati.
Le proprietà ereditate e quelle personalizzate superano il confine
Le proprietà CSS ereditate (color, font-family, line-height e le proprietà personalizzate CSS) vengono trasmesse attraverso il confine dello shadow DOM. In questo modo, le proprietà personalizzate diventano l'API di tematizzazione principale: esponile con fallback ragionevoli.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Una proprietà personalizzata definita su :host può essere sostituita solo da regole che hanno come target specifico l'elemento host. Per consentire l'ereditarietà e l'override da qualsiasi punto dell'albero, lascia la proprietà pubblica non impostata su :host e utilizza il valore di riserva in ogni var(); non eseguire il seeding della proprietà pubblica su :host.
Per mantenere il valore di riserva dichiarato una sola volta anziché ripetuto in ogni var(), definisci una proprietà "shadow" privata su :host che legge quella pubblica e fai riferimento alla proprietà privata internamente:
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
I consumer eseguono comunque l'override di --card-bg dall'esterno; il trattino basso iniziale indica che l'alias è interno e il fallback si trova in un unico punto.
::slotted(): applicazione di stili al Light DOM proiettato
::slotted(selector) ha come target i nodi di primo livello assegnati a uno slot. Non può raggiungere i relativi discendenti e perde rispetto a qualsiasi regola del DOM Light (il consumer è proprietario dei propri contenuti), quindi utilizzalo per piccoli ritocchi, non per il controllo. ::slotted(...) !important può avere la meglio sulle regole del consumer, ma in questo modo viene eseguito l'override dello stile che il consumer ha il diritto di possedere. Riservalo per le correzioni strutturali, non per gli override estetici.
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() e exportparts: espongono gli elementi interni per i temi
Contrassegna un elemento interno con part="name" per consentire ai consumatori di applicare uno stile con qualsiasi proprietà, senza dover apprendere la struttura interna.
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
Esponi alcune parti di alto livello (pulsante, input, etichetta), non ogni <div>; le superfici ampie delle parti diventano un'API fragile. Per esporre nuovamente le parti di un componente nidificato verso l'alto tramite il tuo componente, inoltrale con exportparts:
<inner-widget exportparts="submit: card-submit"></inner-widget>
Scegli tra i due canali in modo deliberato: proprietà personalizzate per un insieme di valori limitato e sicuro per il sistema di progettazione; parti per uno stile senza restrizioni di un elemento specifico.
Inoltro degli stili a un elemento nativo con wrapping
Quando un elemento personalizzato racchiude un controllo nativo (<my-button> attorno a un <button> reale), tre cose di solito creano problemi:
- I controlli dei moduli non ereditano la tipografia.
colore le proprietà personalizzate superano il confine, ma<button>,<input>e<select>prendonofonte gli elementi correlati dal foglio di stile UA, che non eredita. Il controllo con wrapping viene quindi visualizzato nel carattere della piattaforma, non in quello della pagina, finché non impostifont: inherit(e di solitocolor: inherit) al suo interno nello shadow tree. Questo è l'errore più comune. - Gli stili
:hostnon raggiungono l'elemento interno. L'impostazione dibackground/border/paddingsu:hostapplica lo stile alla casella host, non al controllo al suo interno. Inoltra deliberatamente: esponi il controllo come::partper uno stile aperto o passa un insieme fisso di valori tramite proprietà personalizzate. Una regola:hostnon viene applicata a cascata all'elemento con wrapping. display: contents+all: inheritè un vero e proprio compromesso, non una scorciatoia. La compressione dell'host condisplay: contentse la reimpostazione del controllo interno conall: inheritè attualmente l'unico modo generico per passare stili arbitrari a un elemento nativo con wrapping senza che i consumer debbano apprendere parti per componente o proprietà personalizzate. Inoltre, la rimozione della casella dell'host è solitamente lo scopo del pattern (la composizione compensa un'ereditarietà integrata mancante). Due avvertenze reali:all: inheritcancella i valori predefiniti di UA, quindi limita l'ambito a proprietà specifiche o utilizzarevertquando vuoi ripristinare i valori predefiniti; e il blocco concreto:getBoundingClientRect()egetClientRects()restituiscono zero per gli elementidisplay: contents, quindi il posizionamento ancorato rispetto all'host non funziona. Utilizza::parto le proprietà personalizzate quando questi compromessi non sono adatti.
:state(): applica lo stile agli stati del componente
Un elemento personalizzato può esporre stati booleani interni tramite ElementInternals tramite this.#internals.states.add('busy') e .delete('busy') (consulta Elementi personalizzati per l'utilizzo del ciclo di vita) e questi stati diventano un canale di stile oltre il confine, come ::part e le proprietà personalizzate, ma per lo stato anziché per la struttura o i valori. Abbinali all'interno dello shadow tree con :host(:state(name)) e dal foglio di stile di un consumer con tag-name:state(name):
/* Inside the component's shadow tree. */
:host(:state(busy)) button { opacity: 0.6; pointer-events: none; }
/* From the consumer's stylesheet — a read-only public hook. */
favorite-button:state(favorited) { filter: drop-shadow(0 0 4px crimson); }
A differenza di un attributo riflesso, uno stato personalizzato è di sola lettura per l'esterno: i consumer possono applicare uno stile, ma non possono impostarlo o falsificarlo, quindi il componente rimane l'unico writer. Preferiscilo a un attributo data-* ogni volta che lo stato è di proprietà del componente e deve solo essere stilizzato.
@layer e query container all'interno dei componenti
- I livelli di struttura a cascata (
@layer) funzionano all'interno di una shadow root e hanno un ambito limitato. Il nome di un livello nello shadow tree è indipendente da un livello con lo stesso nome nella pagina. Utilizzali per ordinare gli stili dei tuoi componenti (ad es.@layer base, theme;) in modo che gli override dei consumer tramite proprietà personalizzate e::partcontinuino a vincere in modo prevedibile. - Le query container sono lo strumento giusto per i componenti adattabili: un componente deve adattarsi allo spazio che gli viene assegnato, non alla viewport. Dichiara l'host o un wrapper come contenitore (assegnando a
:hostuna casella di layout comedisplay: block, poiché gli elementi personalizzati sono impostati sudisplay: inlineper impostazione predefinita e il contenimento delle dimensioni non si applica agli elementiinlineocontents) ed esegui una query, in modo che lo stesso componente si adatti a una barra laterale o a un'area a larghezza intera.
:host {
display: block; /* Required: size containment does not apply to default display: inline */
container-type: inline-size;
}
@container (min-width: 30rem) {
.card { grid-template-columns: 1fr 2fr; }
}
Strategie di riserva
::part, i livelli di visualizzazione a cascata e le query container sono ampiamente disponibili; applica i temi principalmente tramite proprietà personalizzate ereditate, che funzionano anche dove queste non funzionano. Come indicato sopra, non fare affidamento su :host-context(), che è effettivamente solo per Chromium e non è un'opzione orientata al futuro.