W3docs

Estilização do Shadow DOM

Estilize web components encapsulados com o Shadow DOM: :host, :host(), :host-context(), ::slotted(), propriedades customizadas CSS que atravessam o limite, e adoptedStyleSheets.

O Shadow DOM dá a um componente sua própria árvore DOM privada e seus próprios estilos privados. Esta página foca na parte de estilização: como o CSS escrito dentro de um shadow root é encapsulado, os seletores especiais usados para alcançar o host e o conteúdo inserido via slot (:host, :host(), :host-context(), ::slotted()), como permitir que o CSS externo personalize um componente propositalmente (propriedades customizadas), e as duas formas de anexar uma folha de estilos (<style> vs adoptedStyleSheets).

Se o Shadow DOM é novidade para você, leia primeiro JavaScript Shadow DOM e consulte Web Components para uma visão geral de onde os shadow roots se encaixam.

Por que os estilos são encapsulados

A promessa central do Shadow DOM é uma barreira de estilo bidirecional:

  • O CSS externo não vaza para dentro. Uma regra de página inteira como p { color: red } não afetará um <p> dentro de um shadow root. É isso que torna os componentes seguros para usar em qualquer página.
  • O CSS interno não vaza para fora. Estilos em um shadow root se aplicam apenas dentro daquele root, então você pode usar seletores curtos e genéricos (button, p, .title) sem se preocupar com conflitos com a página host.

Isso é diferente do modelo regular de estilos e classes, onde cada seletor compete em um escopo global único. Dentro de um shadow root, o escopo é o padrão.

Criando um shadow root

Para começar, anexe um shadow root a um elemento host. Tudo o que você colocar dentro dele — marcação e CSS — fica encapsulado.

<body>
  <div id="my-element"></div>
    <script>
      // Creating Shadow DOM
      const shadowRoot = document.getElementById('my-element').attachShadow({ mode: 'open' });
    
      // Styling Shadow DOM
      shadowRoot.innerHTML = `
        <p>A simple shadow root content.</p>
      `;
    </script>
</body>

Aqui anexamos um shadow root com o método attachShadow() e definimos seu mode como 'open', o que permite ler o root posteriormente via element.shadowRoot. ('closed' o oculta de scripts externos, mas não adiciona segurança real.)

Adicionando estilos com escopo usando <style>

A forma mais simples de estilizar um shadow root é colocar um elemento <style> dentro dele. Essas regras se aplicam apenas dentro do root — e as regras da página ficam de fora.

<div id="my-element">
  <!-- Shadow DOM content -->
</div>

<script>
  const shadowRoot = document.getElementById('my-element').attachShadow({ mode: 'open' });

  shadowRoot.innerHTML = `
    <style>
      /* Scoped styles */
      :host {
        display: block;
        border: 2px solid #333;
        padding: 10px;
      }

      p {
        color: blue;
      }
    </style>

    <p>This paragraph is styled within the Shadow DOM.</p>
  `;
</script>

A regra p { color: blue } colore apenas o parágrafo dentro deste root — um <p> em outro lugar na página não é afetado. A regra :host (abaixo) estiliza o próprio elemento host.

Selecionando o host: :host, :host(), :host-context()

Um shadow root não pode selecionar seu elemento host com um seletor normal, porque o host vive fora do root. Três pseudo-classes fazem essa ponte:

SeletorCorresponde aUse para
:hostO elemento host, sempreEstilos base do componente (display, padding, box).
:host(<seletor>)O host somente quando corresponde a <seletor>Variantes e estados controlados por atributos/classes/pseudo-classes, ex.: :host([disabled]), :host(:hover).
:host-context(<seletor>)O host quando um ancestral corresponde a <seletor>Adaptar ao contexto, ex.: :host-context(.dark-theme).
<div class="dark-theme">
  <fancy-box disabled>Boxed content</fancy-box>
</div>

<script>
  class FancyBox extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' }).innerHTML = `
        <style>
          :host {
            display: block;
            padding: 12px;
            border: 2px solid #007bff;
          }
          /* Variant: applies only when the host has [disabled] */
          :host([disabled]) {
            opacity: 0.5;
            pointer-events: none;
          }
          /* Context: applies when any ancestor has .dark-theme */
          :host-context(.dark-theme) {
            background: #1e1e1e;
            color: #fff;
          }
        </style>
        <slot></slot>
      `;
    }
  }
  customElements.define('fancy-box', FancyBox);
</script>

Como o elemento host começa com [disabled] e está dentro de .dark-theme, todas as três regras se aplicam: ele é renderizado escuro, com opacidade reduzida e sem interatividade.

Aviso

:host-context() tem suporte limitado nos navegadores (sem Firefox no momento da escrita). Prefira uma propriedade customizada CSS ou um atributo explícito no host quando precisar de ampla compatibilidade.

Estilizando conteúdo inserido via slot com ::slotted()

O conteúdo que o usuário passa para o seu componente vive no light DOM e é renderizado através de um <slot>. Esse conteúdo continua pertencendo à página, então os estilos da própria página prevalecem — mas você ainda pode alcançá-lo de dentro do shadow root com ::slotted().

Um limite importante: ::slotted() corresponde apenas aos nós inseridos via slot de nível superior, não aos seus descendentes. ::slotted(span) funciona; ::slotted(div span) não funciona.

<body>
<script>
  class CustomButton extends HTMLElement {
    constructor() {
      super();
      const shadowRoot = this.attachShadow({ mode: 'open' });

      shadowRoot.innerHTML = `
        <style>
          :host {
            display: inline-block;
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            border: none;
            cursor: pointer;
          }

          :host(:hover) {
            background-color: #0056b3;
          }

          button {
            font-weight: bold;
            border: none;
            background: none;
            color: inherit;
            cursor: inherit;
            padding: 0;
          }

          /* Styling slotted content */
          ::slotted(span) {
            font-style: italic;
            text-decoration: underline;
          }
        </style>

        <button>
          <slot></slot>
        </button>
      `;
    }
  }

  customElements.define('custom-button', CustomButton);
</script>

<!-- Test custom-button with slotted content -->
<custom-button id="my-button">Click <span>here</span></custom-button>
</body>

Aqui ::slotted(span) seleciona o <span> passado como conteúdo do slot, aplicando itálico e sublinhado, enquanto o texto "Click" ao redor é deixado sem alteração.

Permitindo que a página personalize um componente: propriedades customizadas CSS

O encapsulamento é ótimo, mas pode parecer uma parede: a página host não consegue alcançar o interior para recolorir um botão. A saída intencional são as propriedades customizadas CSS (variáveis) — elas são a única coisa que atravessa o limite do shadow. O componente lê uma variável com var() e fornece um valor de fallback; a página define essa variável de fora.

<style>
  /* The page customizes the component from outside the boundary */
  theme-button {
    --btn-bg: #28a745;
    --btn-bg-hover: #1e7e34;
  }
</style>

<theme-button>Save</theme-button>

<script>
  class ThemeButton extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' }).innerHTML = `
        <style>
          :host {
            /* var(--name, fallback): fallback is used if the page sets nothing */
            background: var(--btn-bg, #007bff);
            color: #fff;
            padding: 8px 16px;
            display: inline-block;
            cursor: pointer;
          }
          :host(:hover) {
            background: var(--btn-bg-hover, #0056b3);
          }
        </style>
        <slot></slot>
      `;
    }
  }
  customElements.define('theme-button', ThemeButton);
</script>

O botão é renderizado em verde porque a página definiu --btn-bg. Remova essas duas declarações e ele volta ao azul (#007bff) como fallback. Esta é a forma mais limpa de expor uma API de tematização enquanto mantém os internos do componente privados.

<style> vs adoptedStyleSheets

Colocar uma tag <style> no innerHTML de cada instância funciona, mas duplica o texto CSS para cada componente na página e força o navegador a analisá-lo novamente cada vez. Para componentes criados muitas vezes, compartilhe um único CSSStyleSheet analisado entre os roots com adoptedStyleSheets.

<my-badge>New</my-badge>
<my-badge>Beta</my-badge>

<script>
  // Parsed once, reused by every instance
  const sheet = new CSSStyleSheet();
  sheet.replaceSync(`
    :host {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 999px;
      background: #007bff;
      color: #fff;
      font-size: 12px;
    }
  `);

  class MyBadge extends HTMLElement {
    constructor() {
      super();
      const root = this.attachShadow({ mode: 'open' });
      root.adoptedStyleSheets = [sheet]; // adopt the shared sheet
      root.innerHTML = `<slot></slot>`;
    }
  }
  customElements.define('my-badge', MyBadge);
</script>

Quando usar cada um:

  • <style> dentro do root — mais simples, sem código extra, adequado para componentes únicos ou pequenas demonstrações.
  • adoptedStyleSheets — preferível quando o mesmo componente aparece muitas vezes: uma folha de estilos construtível e compartilhada significa menos memória e instanciação mais rápida. Você também pode atualizar a folha em tempo de execução (sheet.replaceSync(...)) e todo root que a adotou reflete a mudança instantaneamente.

Conclusão

A estilização com Shadow DOM repousa em algumas ideias: os estilos têm escopo nos dois sentidos, você alcança o host com :host / :host() / :host-context(), você alcança o conteúdo projetado com ::slotted(), você expõe tematização através de propriedades customizadas CSS, e você anexa CSS de forma inline com <style> ou de forma eficiente com adoptedStyleSheets. Em conjunto, eles permitem que você publique componentes que ficam corretos em qualquer lugar e ainda assim permanecem personalizáveis nos seus termos.

Para ir mais longe, veja Shadow DOM Slots & Composition para saber como o conteúdo inserido via slot é montado, e Web Components para combinar shadow roots com elementos customizados e templates.

Prática

Prática
Qual recurso do Shadow DOM permite que os desenvolvedores estilizem conteúdo projetado em elementos customizados?
Qual recurso do Shadow DOM permite que os desenvolvedores estilizem conteúdo projetado em elementos customizados?
Was this page helpful?