Shadow DOM ist eine Stilgrenze: Seitenregeln dringen nicht ein und Komponentenregeln dringen nicht nach außen, mit Ausnahme von geerbten Eigenschaften (einschließlich benutzerdefinierter Eigenschaften). Über die unten aufgeführten Kanäle kannst du sie gezielt überqueren.
: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; }
Vermeide :host-context(). Sie reicht über die Grenze hinaus, um übergeordnete Elemente zu testen. Firefox und Safari haben sich jedoch geweigert, sie zu implementieren, und die CSSWG tendiert dazu, sie nicht weiterzuentwickeln. Betrachten Sie sie als Chromium-spezifische Sackgasse, nicht als zukunftsorientierte Option. Stattdessen wird das Design über eine übernommene benutzerdefinierte CSS-Eigenschaft festgelegt (siehe unten): Der Nutzer legt die Eigenschaft für ein übergeordnetes Element fest und die Komponente liest sie über die Grenze hinweg. Das funktioniert überall und hält beide Seiten entkoppelt.
Übernommene und benutzerdefinierte Eigenschaften überschreiten die Grenze
Übernommene CSS-Properties (color, font-family, line-height und benutzerdefinierte CSS-Properties) werden über die Shadow-Grenze hinweg weitergegeben. Dadurch werden benutzerdefinierte Eigenschaften zur primären API für das Design: Stellen Sie sie mit sinnvollen Fallbacks bereit.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Eine benutzerdefinierte Eigenschaft, die für :host definiert ist, kann nur durch Regeln überschrieben werden, die speziell auf das Hostelement ausgerichtet sind. Wenn Sie möchten, dass die öffentliche Property von einer beliebigen Stelle im Baum übernommen und überschrieben werden kann, lassen Sie die öffentliche Property in :host ungesetzt und verlassen Sie sich auf den Fallback in jedem var(). Legen Sie die öffentliche Property nicht in :host fest.
Damit der Fallback nur einmal deklariert wird, anstatt bei jedem var() wiederholt zu werden, definieren Sie ein privates „Schatten“-Attribut für :host, das das öffentliche Attribut liest, und verweisen Sie intern auf das private Attribut:
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
Nutzer können --card-bg weiterhin von außen überschreiben. Der führende Unterstrich signalisiert, dass der Alias intern ist, und der Fallback befindet sich an genau einer Stelle.
::slotted(): Projiziertes Light DOM formatieren
::slotted(selector) zielt auf die Knoten der obersten Ebene ab, die einem Slot zugewiesen sind. Sie kann nicht auf ihre Nachfolger angewendet werden und wird von jeder Light DOM-Regel überschrieben (der Nutzer ist Eigentümer seiner eigenen Inhalte). Verwenden Sie sie daher für kleine Anpassungen, nicht für die Steuerung. ::slotted(...) !important kann Nutzerregeln überschreiben, aber dadurch werden auch Stile überschrieben, die der Nutzer festlegen darf. Verwenden Sie es daher nur für strukturelle Korrekturen, nicht für ästhetische Überschreibungen.
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() und exportparts: Interna für das Design verfügbar machen
Markieren Sie ein internes Element mit part="name", damit Nutzer es mit einem beliebigen Attribut gestalten können, ohne Ihre interne Struktur kennen zu müssen.
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
Stellen Sie nur einige übergeordnete Elemente (Schaltfläche, Eingabe, Label) bereit, nicht jedes <div>. Breite Teileoberflächen führen zu einer instabilen API. Wenn Sie die Teile einer verschachtelten Komponente über Ihre eigene Komponente wieder verfügbar machen möchten, leiten Sie sie mit exportparts weiter:
<inner-widget exportparts="submit: card-submit"></inner-widget>
Wählen Sie bewusst zwischen den beiden Kanälen: Benutzerdefinierte Eigenschaften für einen eingeschränkten, designsystemsicheren Satz von Werten; Teile für uneingeschränktes Styling eines bestimmten Elements.
Stile an ein umschlossenes natives Element weiterleiten
Wenn ein benutzerdefiniertes Element ein natives Steuerelement umschließt (<my-button> um ein echtes <button>), gibt es drei Dinge, die häufig zu Problemen führen:
- Formularsteuerelemente übernehmen die Typografie nicht.
colorund benutzerdefinierte Eigenschaften überschreiten die Grenze, aber<button>,<input>und<select>übernehmenfontund ähnliche Eigenschaften aus dem UA-Stylesheet, das nicht vererbt wird. Das umschlossene Steuerelement wird daher in der Schriftart der Plattform und nicht in der Schriftart der Seite gerendert, bis Siefont: inherit(und normalerweisecolor: inherit) im Shadow-Baum festlegen. Das ist der häufigste Fehler. :host-Stile erreichen das innere Element nicht. Wenn Siebackground/border/paddingfür:hostfestlegen, wird das Hostfeld formatiert, nicht das darin enthaltene Steuerelement. Gezielt weiterleiten: Machen Sie das Steuerelement als::partfür ein offenes Styling verfügbar oder leiten Sie eine feste Gruppe von Werten über benutzerdefinierte Eigenschaften weiter. Eine:host-Regel wird nicht auf das umschlossene Element angewendet.display: contents+all: inheritist ein echter Kompromiss, keine Abkürzung. Das Minimieren des Hosts mitdisplay: contentsund das Zurücksetzen des inneren Steuerelements mitall: inheritist derzeit die einzige allgemeine Möglichkeit, beliebige Stile an ein umschlossenes natives Element weiterzugeben, ohne dass Nutzer komponentenbezogene Teile oder benutzerdefinierte Eigenschaften lernen müssen. Das Entfernen der Box des Hosts ist normalerweise der Zweck des Musters (Komposition kompensiert eine fehlende integrierte Vererbung). Zwei echte Einschränkungen:all: inheritlöscht UA-Standardwerte, also beschränken Sie den Bereich auf bestimmte Eigenschaften oder verwenden Sierevert, wenn Sie die Standardwerte wiederherstellen möchten. Und der konkrete Blocker:getBoundingClientRect()undgetClientRects()geben fürdisplay: contents-Elemente null zurück, sodass die Ankerpositionierung gegenüber dem Host nicht funktioniert. Verwenden Sie::partoder benutzerdefinierte Eigenschaften, wenn diese Kompromisse nicht passen.
:state(): die eigenen Zustände der Komponente gestalten
Ein benutzerdefiniertes Element kann interne boolesche Zustände über ElementInternals über this.#internals.states.add('busy') und .delete('busy') verfügbar machen (siehe Benutzerdefinierte Elemente für die Verwendung des Lebenszyklus). Diese Zustände werden zu einem Styling-Kanal über die Grenze hinweg – wie ::part und benutzerdefinierte Eigenschaften, aber für den Zustand und nicht für die Struktur oder Werte. Gleichen Sie sie innerhalb des Shadow-Trees mit :host(:state(name)) und aus dem Stylesheet eines Nutzers mit tag-name:state(name) ab:
/* 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); }
Im Gegensatz zu einem reflektierten Attribut ist ein benutzerdefinierter Status von außen schreibgeschützt: Nutzer können ihn zwar formatieren, aber nicht festlegen oder fälschen. Die Komponente bleibt also der einzige Writer. Verwenden Sie es anstelle eines data-*-Attributs, wenn der Status der Komponente gehört und nur formatiert werden muss.
@layer und Containerabfragen in Komponenten
- Kaskadenebenen (
@layer) funktionieren innerhalb eines Shadow-Roots und sind auf diesen beschränkt. Ein Ebenenname im Shadow-Tree ist unabhängig von einer gleichnamigen Ebene auf der Seite. Verwenden Sie sie, um Ihre eigenen Komponentenstile (z.B.@layer base, theme;) zu ordnen, damit Überschreibungen durch Nutzer über benutzerdefinierte Eigenschaften und::partweiterhin vorhersehbar funktionieren. - Containerabfragen sind das richtige Tool für responsive Komponenten: Eine Komponente sollte auf den ihr zugewiesenen Platz reagieren, nicht auf den Darstellungsbereich. Deklarieren Sie den Host oder einen Wrapper als Container (geben Sie
:hosteine Layoutbox wiedisplay: block, da benutzerdefinierte Elemente standardmäßigdisplay: inlinesind und die Größenbeschränkung nicht fürinline- odercontents-Elemente gilt) und fragen Sie ihn ab, damit sich dieselbe Komponente in einer Seitenleiste oder einem Bereich mit voller Breite anpasst.
: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; }
}
Fallback-Strategien
::part, Kaskadenebenen und Containerabfragen sind weit verbreitet. Das Design wird hauptsächlich über übernommene benutzerdefinierte Eigenschaften festgelegt, die auch dann funktionieren, wenn diese nicht funktionieren. Wie bereits erwähnt, sollten Sie sich nicht auf :host-context() verlassen. Diese Option ist nur für Chromium verfügbar und nicht zukunftssicher.