O Shadow DOM é um limite de estilo: as regras da página não entram e as regras do componente não saem, exceto as propriedades herdadas (incluindo as personalizadas). Cruze-as deliberadamente pelos canais abaixo.
: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; }
Evite :host-context(). Ele alcança fora do limite para testar ancestrais, mas o Firefox e o Safari se recusaram a enviá-lo, e o CSSWG está inclinado a não desenvolvê-lo mais. Trate-o como um beco sem saída exclusivo do Chromium, não como uma opção voltada para o futuro. Tema por uma propriedade personalizada de CSS herdada (abaixo): o consumidor define a propriedade em um ancestral, e o componente a lê além do limite, o que funciona em qualquer lugar e mantém os dois lados separados.
As propriedades herdadas e personalizadas cruzam o limite
As propriedades CSS herdadas (color, font-family, line-height e propriedades CSS personalizadas) atravessam o limite de sombra. Isso faz com que as propriedades personalizadas sejam a principal API de temas: exponha-as com substitutos sensíveis.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Uma propriedade personalizada definida em :host só pode ser substituída por regras que segmentam especificamente o elemento host. Para permitir que ela seja herdada e substituível em qualquer lugar da árvore, deixe a propriedade pública sem definição em :host e use o substituto em cada var(). Não defina a propriedade pública em :host.
Para manter o substituto declarado uma vez em vez de repetido em cada var(), defina uma propriedade "shadow" particular em :host que leia a propriedade pública e faça referência à propriedade particular internamente:
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
Os consumidores ainda substituem --card-bg de fora. O sublinhado inicial indica que o alias é interno, e o substituto fica em um único lugar.
::slotted(): estilização do Light DOM projetado
::slotted(selector) segmenta os nós de nível superior atribuídos a um slot. Ele não pode alcançar os descendentes e perde para qualquer regra do DOM leve (o consumidor é o proprietário do próprio conteúdo). Portanto, use-o para retoques leves, não para controle. ::slotted(...) !important pode substituir as regras do consumidor, mas isso substitui o estilo que o consumidor tem direito de possuir. Reserve-o para correções estruturais, não para substituições estéticas.
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() e exportparts: expor elementos internos para temas
Marque um elemento interno com part="name" para permitir que os consumidores o estilizem com qualquer propriedade, sem precisar aprender sua estrutura interna.
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
Exponha algumas partes de alto nível (botão, entrada, rótulo), não todas as <div>. Superfícies de partes amplas se tornam uma API frágil. Para reexpor as partes de um componente aninhado no seu próprio componente, encaminhe-as com exportparts:
<inner-widget exportparts="submit: card-submit"></inner-widget>
Escolha entre os dois canais de forma deliberada: propriedades personalizadas para um conjunto de valores restrito e seguro para o sistema de design; partes para estilização irrestrita de um elemento específico.
Como encaminhar estilos para um elemento nativo encapsulado
Quando um elemento personalizado envolve um controle nativo (<my-button> em torno de um <button> real), três coisas costumam confundir as pessoas:
- Os controles de formulário não herdam a tipografia.
colore as propriedades personalizadas cruzam o limite, mas<button>,<input>e<select>usamfonte outros elementos da folha de estilo do UA, que não é herdada. Portanto, o controle encapsulado é renderizado na fonte da plataforma, não na fonte da página, até que você definafont: inherit(e geralmentecolor: inherit) nele dentro da árvore de sombra. Esse é o erro mais comum. - Os estilos de
:hostnão alcançam o elemento interno. Definirbackground/border/paddingem:hostestiliza a caixa de host, não o controle dentro dela. Encaminhe deliberadamente: exponha o controle como um::partpara estilização aberta ou canalize um conjunto fixo de valores por propriedades personalizadas. Uma regra:hostnão se propaga em cascata para o elemento encapsulado. display: contents+all: inherité uma compensação real, não um atalho. Recolher o host comdisplay: contentse redefinir o controle interno comall: inherité, no momento, a única maneira genérica de transmitir estilos arbitrários para um elemento nativo encapsulado sem que os consumidores aprendam partes por componente ou propriedades personalizadas. Além disso, remover a caixa do host geralmente é o objetivo do padrão (composição que compensa uma herança integrada ausente). Duas ressalvas reais:all: inheritlimpa os padrões do UA, então limite o escopo a propriedades específicas ou userevertquando quiser os padrões de volta; e o bloqueador concreto:getBoundingClientRect()egetClientRects()retornam zero para elementosdisplay: contents, então o posicionamento de âncora em relação ao host está quebrado. Use::partou propriedades personalizadas quando essas compensações não forem adequadas.
:state(): estilizar os próprios estados do componente
Um elemento personalizado pode expor estados booleanos internos por meio de ElementInternals usando this.#internals.states.add('busy') e .delete('busy') (consulte Elementos personalizados para uso do ciclo de vida), e esses estados se tornam um canal de estilo além do limite, como ::part e propriedades personalizadas, mas para estado em vez de estrutura ou valores. Faça a correspondência deles dentro da árvore de sombra com :host(:state(name)) e de uma folha de estilo do consumidor com 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); }
Ao contrário de um atributo refletido, um estado personalizado é somente leitura para o exterior: os consumidores podem estilizá-lo, mas não podem defini-lo ou forjá-lo, então o componente continua sendo o único gravador. Prefira-o em vez de um atributo data-* sempre que o estado for de propriedade do componente e só precisar ser estilizado.
@layer e consultas de contêiner dentro de componentes
- As camadas em cascata (
@layer) funcionam dentro de uma raiz de sombra e têm escopo definido para ela. Um nome de camada na árvore de sombra é independente de uma camada com o mesmo nome na página. Use-as para ordenar seus próprios estilos de componente (por exemplo,@layer base, theme;) para que as substituições do consumidor por propriedades personalizadas e::partainda vençam de forma previsível. - As consultas de contêiner são a ferramenta certa para componentes responsivos: um componente precisa responder ao espaço que recebe, não à janela de visualização. Declare o host ou um wrapper como um contêiner (fornecendo a
:hostuma caixa de layout comodisplay: block, já que os elementos personalizados usamdisplay: inlinepor padrão e a contenção de tamanho não se aplica a elementosinlineoucontents) e consulte-o para que o mesmo componente se adapte em uma barra lateral ou em uma região de largura total.
: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; }
}
Estratégias de fallback
::part, camadas em cascata e consultas de contêiner estão amplamente disponíveis. Use temas principalmente com propriedades personalizadas herdadas, que funcionam mesmo onde essas não funcionam. Como observado acima, não use :host-context(). Ele é exclusivo do Chromium e não é uma opção para o futuro.