Le Shadow DOM est une limite de style : les règles de page n'y pénètrent pas et les règles de composant n'en sortent pas, à l'exception des propriétés héritées (y compris les propriétés personnalisées). Franchissez-la délibérément en utilisant les canaux ci-dessous.
:host, :host(), :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; }
Évitez :host-context(). Il s'étend au-delà de la limite pour tester les ancêtres, mais Firefox et Safari ont refusé de l'implémenter, et le CSSWG penche pour ne pas le développer davantage. Considérez-le comme une impasse réservée à Chromium, et non comme une option d'avenir. Utilisez plutôt une propriété CSS personnalisée héritée (ci-dessous) : le consommateur définit la propriété sur un ancêtre et le composant la lit au-delà de la limite, ce qui fonctionne partout et maintient les deux côtés dissociés.
Les propriétés héritées et les propriétés personnalisées franchissent la limite
Les propriétés CSS héritées (color, font-family, line-height et les propriétés CSS personnalisées) passent par la limite du shadow DOM. Les propriétés personnalisées deviennent ainsi l'API de thématisation principale : exposez-les avec des solutions de remplacement judicieuses.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Une propriété personnalisée définie sur :host ne peut être remplacée que par des règles ciblant spécifiquement l'élément hôte. Pour qu'elle soit héritée et puisse être remplacée n'importe où dans l'arborescence, ne définissez pas la propriété publique sur :host et fiez-vous à la valeur de remplacement dans chaque var(). Ne définissez pas la propriété publique sur :host.
Pour que la valeur de remplacement ne soit déclarée qu'une seule fois au lieu d'être répétée à chaque var(), définissez une propriété "shadow" privée sur :host qui lit la propriété publique, puis référencez la propriété privée en interne :
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
Les consommateurs remplacent toujours --card-bg de l'extérieur. Le trait de soulignement de début indique que l'alias est interne et que l'élément de remplacement se trouve à un seul endroit.
::slotted() : styliser le Light DOM projeté
::slotted(selector) cible les nœuds de premier niveau attribués à un emplacement. Elle ne peut pas atteindre leurs descendants et est supplantée par toute règle Light DOM (le consommateur est propriétaire de son propre contenu). Utilisez-la donc pour de légères retouches, et non pour le contrôle. ::slotted(...) !important peut l'emporter sur les règles du consommateur, mais cela remplace le style auquel le consommateur a droit. Réservez-le pour les corrections structurelles, et non pour les remplacements esthétiques.
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() et exportparts : exposer les éléments internes pour la thématisation
Marquez un élément interne avec part="name" pour permettre aux utilisateurs de le styliser avec n'importe quelle propriété, sans avoir à connaître votre structure interne.
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
Exposez quelques parties de haut niveau (bouton, entrée, libellé), et non chaque <div>. Les surfaces de composants larges deviennent une API fragile. Pour réexposer les parties d'un composant imbriqué vers le haut via votre propre composant, transférez-les avec exportparts :
<inner-widget exportparts="submit: card-submit"></inner-widget>
Choisissez délibérément entre les deux canaux : les propriétés personnalisées pour un ensemble de valeurs limité et sécurisé pour le système de conception, ou les parties pour un style sans restriction d'un élément spécifique.
Transférer des styles vers un élément natif encapsulé
Lorsqu'un élément personnalisé encapsule un contrôle natif (<my-button> autour d'un véritable <button>), trois problèmes se posent généralement :
- Les commandes de formulaire n'héritent pas de la typographie.
coloret les propriétés personnalisées franchissent la limite, mais<button>,<input>et<select>prennentfontet les éléments associés de la feuille de style UA, qui n'est pas héritée. Le contrôle encapsulé s'affiche donc dans la police de la plate-forme, et non dans celle de la page, jusqu'à ce que vous définissiezfont: inherit(et généralementcolor: inherit) sur celui-ci dans l'arborescence DOM fantôme. C'est l'erreur la plus courante. - Les styles
:hostn'atteignent pas l'élément interne. La définition debackground/border/paddingsur:hostapplique un style à la zone hôte, et non au contrôle qu'elle contient. Transférez délibérément : exposez le contrôle en tant que::partpour un style ouvert, ou transmettez un ensemble fixe de valeurs via des propriétés personnalisées. Une règle:hostne se répercute pas en cascade dans l'élément encapsulé. display: contents+all: inheritest un véritable compromis, pas un raccourci. Réduire l'hôte avecdisplay: contentset réinitialiser le contrôle interne avecall: inheritest actuellement le seul moyen générique de transmettre des styles arbitraires à un élément natif encapsulé sans que les consommateurs n'apprennent les parties par composant ou les propriétés personnalisées. Supprimer la boîte de l'hôte est généralement le but du modèle (la composition compense un héritage intégré manquant). Deux mises en garde :all: inheritefface les valeurs par défaut de l'UA. Vous devez donc cibler des propriétés spécifiques ou utiliserrevertlorsque vous souhaitez rétablir les valeurs par défaut. De plus,getBoundingClientRect()etgetClientRects()renvoient zéro pour les élémentsdisplay: contents, ce qui empêche le positionnement de l'ancrage par rapport à l'hôte. Utilisez::partou des propriétés personnalisées lorsque ces compromis ne conviennent pas.
:state() : styliser les états du composant
Un élément personnalisé peut exposer des états booléens internes via ElementInternals via this.#internals.states.add('busy') et .delete('busy') (voir Éléments personnalisés pour l'utilisation du cycle de vie). Ces états deviennent un canal de style au-delà de la limite, comme ::part et les propriétés personnalisées, mais pour l'état plutôt que pour la structure ou les valeurs. Faites-les correspondre dans l'arborescence shadow avec :host(:state(name)), et à partir de la feuille de style d'un consommateur avec 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); }
Contrairement à un attribut reflété, un état personnalisé est en lecture seule pour l'extérieur : les consommateurs peuvent le styliser, mais ne peuvent pas le définir ni le falsifier. Le composant reste donc le seul rédacteur. Privilégiez-le par rapport à un attribut data-* chaque fois que l'état appartient au composant et qu'il n'a besoin que d'être stylisé.
@layer et requêtes de conteneur dans les composants
- Les couches en cascade (
@layer) fonctionnent dans une racine fantôme et sont limitées à celle-ci. Le nom d'une couche dans l'arborescence fantôme est indépendant d'une couche portant le même nom dans la page. Utilisez-les pour ordonner vos propres styles de composants (par exemple,@layer base, theme;) afin que les remplacements des consommateurs via des propriétés personnalisées et::partl'emportent toujours de manière prévisible. - Les requêtes de conteneur sont l'outil idéal pour les composants responsifs : un composant doit répondre à l'espace qui lui est attribué, et non à la fenêtre d'affichage. Déclarez l'hôte ou un wrapper en tant que conteneur (en attribuant à
:hostune zone de mise en page telle quedisplay: block, car les éléments personnalisés sont définis par défaut surdisplay: inlineet le confinement de la taille ne s'applique pas aux élémentsinlineoucontents) et interrogez-le, afin que le même composant s'adapte dans une barre latérale ou une région pleine largeur.
: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; }
}
Stratégies de remplacement
::part, les couches en cascade et les requêtes de conteneur sont largement disponibles. Le thème est principalement basé sur des propriétés personnalisées héritées, qui fonctionnent même lorsque ces fonctionnalités ne sont pas disponibles. Comme indiqué ci-dessus, ne vous fiez pas à :host-context(), car il s'agit d'une option réservée à Chromium et non d'une option d'avenir.