Back

Estilizando Web Components com Shadow DOM e CSS

Estilizando Web Components com Shadow DOM e CSS

Se você já colocou um Web Component em uma página e se perguntou por que seu CSS global parou de funcionar dentro dele, é porque encontrou a fronteira do shadow. Não é um bug — esse é justamente o objetivo. O Shadow DOM oferece aos elementos personalizados sua própria árvore DOM com escopo isolado, o que significa que os estilos não vazam para dentro nem para fora por padrão. Mas isso não quer dizer que a estilização seja impossível. Significa que a estilização é intencional.

Este artigo aborda os principais mecanismos para estilização com Shadow DOM: estilos internos, :host, ::slotted(), propriedades CSS personalizadas, ::part() e adoptedStyleSheets.

Pontos-Chave

  • O Shadow DOM define o escopo dos estilos por design, então o CSS global não consegue alcançar o interior de um componente e os estilos internos não vazam para fora.
  • Os estilos do shadow funcionam como padrões e ficam abaixo dos estilos da página na cascata, de forma semelhante à folha de estilo do user-agent do navegador.
  • Use :host e :host() para estilizar o elemento do componente, e ::slotted() para estilizar elementos filhos projetados do light DOM (apenas descendentes diretos).
  • As propriedades CSS personalizadas e ::part() formam uma API pública e deliberada de estilização: variáveis para tokens de tematização e parts para estilização estrutural.
  • O adoptedStyleSheets permite que múltiplos componentes compartilhem uma única CSSStyleSheet já parseada, melhorando o desempenho em larga escala.

Por Que o Shadow DOM Muda o Comportamento do CSS

Quando você chama attachShadow() em um elemento, cria uma árvore DOM separada anexada a esse elemento (o shadow host). Os seletores da folha de estilo da sua página não conseguem alcançar o interior dessa árvore, e os seletores dentro da shadow tree não conseguem alcançar o que está fora dela.

Há uma nuance importante que vale a pena entender: os estilos do shadow DOM ficam abaixo dos seus estilos na cascata CSS. Isso significa que uma regra global como * { color: red } vai sobrescrever uma regra :host { color: green } dentro do shadow — mesmo que a regra global apareça antes na ordem do código-fonte. Pense nos estilos do shadow como os estilos padrão do componente, semelhantes a como a folha de estilo do user-agent do navegador fornece padrões para <button> ou <input>.

Estilizando o Shadow Host com :host

De dentro da shadow tree, :host seleciona o elemento ao qual o shadow está anexado:

:host {
  display: block;
  font-family: sans-serif;
}

Você também pode usar :host(selector) para aplicar estilos condicionalmente com base em atributos ou classes do host:

:host([disabled]) {
  opacity: 0.5;
  pointer-events: none;
}

Como o elemento host vive no light DOM, os estilos no nível do documento sobrescreverão as regras :host. Se você quiser que os padrões do componente prevaleçam, recorra ao !important dentro do shadow — um dos raros casos em que isso é genuinamente apropriado.

Estilizando Conteúdo de Slots com ::slotted()

Os slots permitem projetar conteúdo do light DOM dentro de uma shadow tree. O pseudo-elemento ::slotted() permite estilizar esses elementos projetados a partir de dentro do shadow:

::slotted(p) {
  margin: 0;
  color: #333;
}

Limitação importante: ::slotted() só corresponde ao elemento diretamente atribuído a um slot — não a seus descendentes. Um seletor como ::slotted(p span) não vai funcionar, e ::slotted() aceita apenas um seletor composto (sem combinadores de descendentes). Para uma estilização mais profunda, confie na herança CSS ou deixe que os estilos do próprio light DOM cuidem disso.

Propriedades CSS Personalizadas Atravessam a Fronteira do Shadow

As variáveis CSS (propriedades personalizadas) atravessam livremente a fronteira do shadow. Isso as torna a ferramenta mais flexível para tematização de Web Components:

/* Na shadow tree */
:host {
  background: var(--card-bg, white);
  color: var(--card-color, black);
}
/* Na folha de estilo da sua página */
my-card {
  --card-bg: #1a1a2e;
  --card-color: #eee;
}

O componente define os ganchos; o consumidor define os valores. Sempre inclua valores de fallback para que o componente funcione sem qualquer configuração externa.

Expondo Ganchos de Estilo com CSS Shadow Parts (::part())

CSS Shadow Parts são a resposta moderna para estilizar elementos internos de um Web Component a partir de fora. Dentro do componente, você marca elementos com o atributo part:

<button part="trigger">Open</button>

Fora do componente, os consumidores podem atingir essa part diretamente usando ::part():

my-dialog::part(trigger) {
  background: royalblue;
  border-radius: 4px;
}

Esta é uma API de estilização intencional — o autor do componente decide o que é exposto. É mais poderosa do que as variáveis CSS para estilização estrutural (layout, bordas, planos de fundo), mantendo, ao mesmo tempo, os detalhes de implementação interna privados.

Compartilhando Estilos de Forma Eficiente com adoptedStyleSheets

O adoptedStyleSheets permite anexar objetos CSSStyleSheet diretamente a uma shadow root. Ele tem amplo suporte nos navegadores modernos e é ideal para bibliotecas de componentes que precisam compartilhar uma folha de estilo parseada entre muitas instâncias:

const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: block; }`);

class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.adoptedStyleSheets = [sheet];
  }
}

customElements.define('my-component', MyComponent);

O navegador parseia a folha de estilo uma única vez, independentemente de quantos componentes a utilizem — um ganho significativo de desempenho em larga escala. Você pode adicionar ou remover folhas de estilo modificando o array adoptedStyleSheets, e mudanças em uma CSSStyleSheet compartilhada se aplicam em todos os lugares onde ela foi adotada.

Escolhendo a Abordagem Certa

ObjetivoFerramenta
Estilizar o elemento host do componente:host / :host()
Estilizar conteúdo projetado do light DOM::slotted()
Expor tokens de tematização aos consumidoresPropriedades CSS personalizadas
Expor estilização estrutural aos consumidores::part()
Compartilhar estilos entre muitos componentesadoptedStyleSheets

Conclusão

A estilização com Shadow DOM não é sobre bloquear o CSS — é sobre tornar o encapsulamento explícito. Use estilos internos e :host para padrões, variáveis CSS e ::part() como sua API pública de estilização, e adoptedStyleSheets quando o desempenho em larga escala for importante. Uma vez que você tenha esse modelo mental, estilizar Web Components se torna algo direto.

Perguntas Frequentes

Porque o componente usa Shadow DOM, que cria uma árvore DOM com escopo isolada do documento principal. As folhas de estilo da página não conseguem selecionar elementos dentro da shadow tree. Para estilizar os elementos internos, o autor do componente precisa expor ganchos como propriedades CSS personalizadas ou CSS Shadow Parts através do atributo part, que você então atinge com ::part() de fora.

Use propriedades personalizadas para tokens de design como cores, espaçamentos e fontes, que fluem naturalmente através da herança. Use ::part() quando os consumidores precisarem de controle estrutural sobre um elemento interno específico, como sobrescrever bordas, planos de fundo ou layout em um botão ou cabeçalho. As parts oferecem acesso mais granular, enquanto as variáveis permanecem mais simples e amplas em escopo.

O pseudo-elemento ::slotted() só corresponde a nós de nível superior atribuídos diretamente a um slot, não a seus descendentes. Ele também aceita apenas um seletor composto, então combinadores de descendentes são inválidos. Para estilizar filhos de elementos em slots, confie na herança CSS a partir do elemento em slot ou deixe que a folha de estilo do light DOM do próprio consumidor cuide desses descendentes diretamente.

Sim. Ele é suportado em todos os navegadores modernos evergreen e é a forma recomendada de compartilhar estilos entre muitas shadow roots sem reparse. Uma CSSStyleSheet parseada pode ser anexada a múltiplas shadow roots, o que reduz o consumo de memória e melhora o tempo de inicialização. Folhas de estilo compartilhadas também podem ser atualizadas e reutilizadas entre múltiplos componentes de forma eficiente.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay