Webcomponenten opmaken

Shadow DOM is een stijlgrens: paginaregels dringen niet door en componentregels lekken niet uit, behalve overgenomen eigenschappen (waaronder aangepaste eigenschappen). Kruis het bewust aan via de onderstaande kanalen.

: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; }

Vermijd :host-context(). De functie reikt buiten de grens om voorouders te testen, maar Firefox en Safari hebben geweigerd deze te leveren en de CSSWG neigt ernaar deze niet verder te ontwikkelen. Behandel de functie als een doodlopende weg die alleen voor Chromium geldt, niet als een toekomstgerichte optie. Thema via een overgenomen aangepaste CSS-eigenschap (hieronder): de consument stelt de eigenschap in op een bovenliggend element en de component leest deze over de grens, wat overal werkt en beide kanten ontkoppeld houdt.

Overgenomen eigenschappen en aangepaste eigenschappen overschrijden de grens

Overgenomen CSS-property's (color, font-family, line-height en aangepaste CSS-property's) worden doorgegeven via de schaduwgrens. Hierdoor zijn aangepaste eigenschappen de primaire thema-API: maak ze beschikbaar met verstandige fallbacks.

:host {
  /* Consumers override --card-bg from outside; the fallback keeps it usable. */
  background: var(--card-bg, white);
  color: var(--card-fg, black);
}

Een aangepaste eigenschap die is gedefinieerd op :host, kan alleen worden overschreven door regels die specifiek het hostelement targeten. Als je wilt dat de openbare property wordt overgenomen en overschreven vanaf een hogere positie in de structuur, stel je de openbare property niet in op :host en vertrouw je op de fallback in elke var(). Seed de openbare property niet op :host.

Als je de fallback maar één keer wilt declareren in plaats van deze te herhalen bij elke var(), definieer je een privé-'schaduw'-property op :host die de openbare property leest en verwijs je intern naar de privé-property:

:host {
  --_card-bg: var(--card-bg, white);
  --_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }

Consumenten overschrijven --card-bg nog steeds van buitenaf. De leidende underscore geeft aan dat de alias intern is en de fallback op precies één plek staat.

::slotted(): styling van geprojecteerde Light DOM

::slotted(selector) target de nodes op het hoogste niveau die aan een slot zijn toegewezen. De regel kan de onderliggende elementen niet bereiken en verliest van elke Light DOM-regel (de consument is de eigenaar van de eigen content). Gebruik de regel dus voor kleine aanpassingen, niet voor beheer. ::slotted(...) !important kan consumentenregels overschrijven, maar overschrijft dan ook de stijl waar de consument recht op heeft. Gebruik dit alleen voor structurele oplossingen, niet voor esthetische overschrijvingen.

/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }

::part() en exportparts: interne elementen tonen voor thema's

Markeer een intern element met part="name" zodat consumenten het kunnen stylen met een property, zonder dat ze je interne structuur hoeven te leren.

<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }

Maak een paar overkoepelende onderdelen zichtbaar (knop, invoer, label), niet elk <div>; brede onderdelen worden een kwetsbare API. Als u de onderdelen van een geneste component opnieuw naar boven wilt weergeven via uw eigen component, stuurt u ze door met exportparts:

<inner-widget exportparts="submit: card-submit"></inner-widget>

Kies bewust tussen de 2 kanalen: aangepaste eigenschappen voor een beperkte, voor het ontwerpsysteem veilige set waarden; onderdelen voor onbeperkte styling van een specifiek element.

Stijlen doorsturen naar een verpakt native element

Als een aangepast element een systeemeigen bedieningselement verpakt (<my-button> rond een echte <button>), zijn er 3 dingen die vaak problemen veroorzaken:

  • Formulierbesturingselementen nemen geen typografie over. color en aangepaste eigenschappen overschrijden de grens, maar <button>, <input> en <select> nemen font en vergelijkbare elementen over van de UA-stylesheet, die niet wordt overgenomen. De verpakte bedieningselementen worden daarom weergegeven in het platformlettertype, niet in het paginalettertype, totdat je font: inherit (en meestal color: inherit) instelt in de schaduwstructuur. Dit is de meest voorkomende fout.
  • :host-stijlen bereiken het binnenste element niet. Als je background/border/padding instelt op :host, wordt het hostvak opgemaakt, niet de bedieningselementen erin. Stuur bewust door: maak de bediening beschikbaar als een ::part voor open styling of stuur een vaste set waarden door via aangepaste eigenschappen. Een :host-regel wordt niet trapsgewijs toegepast op het verpakte element.
  • display: contents + all: inherit is een echte afweging, geen snelkoppeling. De host samenvouwen met display: contents en de interne bediening resetten met all: inherit is op dit moment de enige algemene manier om willekeurige stijlen door te geven aan een verpakt native element zonder dat consumenten per component onderdelen of aangepaste eigenschappen leren. De eigen box van de host verwijderen is meestal het doel van het patroon (compositie die compenseert voor een ontbrekende ingebouwde overerving). 2 echte kanttekeningen: all: inherit wist UA-standaardwaarden, dus beperk het bereik tot specifieke property's of gebruik revert als je de standaardwaarden terug wilt. En de concrete blokkering: getBoundingClientRect() en getClientRects() retourneren nul voor display: contents-elementen, dus de ankerpositionering ten opzichte van de host is verbroken. Gebruik ::part of aangepaste eigenschappen als die afwegingen niet passen.

:state(): de eigen statussen van de component opmaken

Een aangepast element kan interne booleaanse statussen tonen via ElementInternals via this.#internals.states.add('busy') en .delete('busy') (ga naar Aangepaste elementen voor gebruik van de levenscyclus). Deze statussen worden een stijlkanal over de grens, zoals ::part en aangepaste eigenschappen, maar voor status in plaats van structuur of waarden. Match ze in de schaduwstructuur met :host(:state(name)) en vanuit de stylesheet van een consument met 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); }

In tegenstelling tot een gereflecteerd kenmerk is een aangepaste status alleen-lezen voor de buitenwereld: consumenten kunnen deze opmaken, maar niet instellen of vervalsen, zodat de component de enige schrijver blijft. Gebruik dit kenmerk in plaats van een data-*-kenmerk als de status eigendom is van de component en alleen moet worden opgemaakt.

@layer en containerquery's in componenten

  • Cascade-lagen (@layer) werken binnen een schaduwroot en zijn daarop gericht. Een laagnaam in de schaduwstructuur is onafhankelijk van een laag met dezelfde naam op de pagina. Gebruik ze om je eigen componentstijlen te ordenen (bijv. @layer base, theme;), zodat overschrijvingen door consumenten via aangepaste eigenschappen en ::part nog steeds voorspelbaar winnen.
  • Containerquery's zijn de juiste tool voor responsieve componenten: een component moet reageren op de ruimte die deze krijgt, niet op de viewport. Declareer de host of een wrapper als container (geef :host een indelingsvak zoals display: block, omdat aangepaste elementen standaard display: inline zijn en formaatbeperking niet van toepassing is op inline- of contents-elementen) en voer een query uit, zodat dezelfde component zich aanpast in een zijbalk of een gebied met volledige breedte.
: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; }
}

Reservestrategieën

::part, cascadelaag en containerquery's zijn breed beschikbaar. Thema's zijn voornamelijk beschikbaar via overgenomen aangepaste eigenschappen, die zelfs werken als deze niet werken. Zoals hierboven vermeld, moet je niet vertrouwen op :host-context(). Dit is in feite alleen voor Chromium en geen toekomstbestendige optie.