Mantenha tokens de design compartilhados no CSS da página, exponha variáveis específicas do componente como API de estilização e conecte-as com um pequeno mapeamento pertencente à página. Este tutorial cria uma demonstração local no Mac que mantém essas responsabilidades separadas.

O que pertence à página e o que pertence ao componente?

Para alguém editando uma página web autorada em um Mac, a tarefa concreta é aplicar tema a um componente reutilizável sem fazer sua implementação depender dos nomes de token da página. O exemplo abaixo usa um componente sintético orbit-tile contendo uma forma abstrata e um rótulo curto.

Um token de design e uma variável pública de componente podem ser ambos propriedades personalizadas CSS. A distinção vem do contrato que você documenta. Propriedades personalizadas comuns não registradas participam da cascata e herdam; declará-las em :root as torna disponíveis por herança em todo o documento. Não há palavra-chave especial em CSS que torne uma propriedade um token de design. Usando propriedades personalizadas CSS (variáveis)

Use esta tabela de propriedade para decidir onde uma declaração pertence. Os nomes são ilustrativos e locais a este exemplo.

Camada Exemplo Responsabilidade
Tokens de design da página --scene-accent, --scene-rounding Escolhas visuais compartilhadas para a página autorada
Regras de integração da página Uma regra alvo orbit-tile Traduz escolhas da página para a API do componente
Variáveis públicas do componente --orbit-tile-fill, --orbit-tile-radius Controles documentados disponíveis para consumidores
Implementação do componente .shape, regras de layout, padrões literais Renderiza o componente sem exigir tokens da página

O teste útil é portabilidade: outra página poderia fornecer nomes de token diferentes enquanto usa o mesmo componente inalterado? Com um mapeamento pertencente à página, poderia. A nova página mapearia seus próprios tokens para as mesmas variáveis públicas do componente.

Como o contrato de estilização atravessa o Shadow DOM?

O Shadow DOM separa correspondência de seletores de herança. Um seletor da página não pode normalmente selecionar uma forma interna através da fronteira do shadow. Valores de propriedades herdados ainda podem alcançá-lo através do host. A especificação CSS Shadow descreve a árvore de elementos achatada usada para esta relação; árvores de seletores separadas não eliminam a herança. Módulo CSS Shadow Nível 1

Mantenha as variáveis públicas não definidas dentro do componente e forneça padrões onde são consumidas. Por exemplo, background-color: var(--orbit-tile-fill, #667085) aceita um valor herdado do consumidor enquanto fornece um padrão autônomo. Declarar --orbit-tile-fill: #667085 em :host daria ao host seu próprio valor e bloquearia um valor herdado de um ancestral. Um consumidor ainda poderia alvejar o host diretamente. Estilizando Web Components

Para este exemplo, documente apenas dois controles públicos:

Variável pública Valor aceito para este exemplo Padrão quando ausente Efeito
--orbit-tile-fill Uma cor CSS válida #667085 Cor de fundo da forma
--orbit-tile-radius Um comprimento ou porcentagem não negativo 1rem Raio dos cantos da forma

Estes valores aceitos são um acordo de API. A implementação base não os registra ou valida automaticamente. Nomes de classes internas são detalhes de implementação, então autores da página devem personalizar as variáveis públicas em vez de depender de .shape.

Construa um exemplo local completo no seu Mac

Use um editor de texto simples e um navegador com suporte a propriedades personalizadas CSS, Shadow DOM e elementos personalizados autônomos. Este exemplo usa um elemento autônomo estendendo HTMLElement; a MDN distingue essa abordagem de elementos built-in personalizados, que o Safari não planeja suportar. Usando elementos personalizados

No TextEdit, escolha Arquivo > Novo, depois Formato > Tornar Texto Simples. Cole o documento completo abaixo e salve como token-demo.html. Se solicitado, escolha Usar .html. Ao reabri-lo para edição, selecione Ignorar comandos de rich text na caixa de diálogo de abertura, ou ative Exibir arquivos HTML como código HTML em vez de texto formatado nas configurações. Trabalhar com documentos HTML no TextEdit no Mac

A primeira folha de estilo do documento possui tokens, regras de integração e layout da página. O componente cria uma folha de estilo separada dentro de sua shadow root. O HTML define o elemento style como forma de embutir CSS; colocar todo o fonte em um arquivo mantém a configuração de recursos do exemplo explícita. HTML Standard: O elemento style

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Orbit tile token demo</title>
  <style>
    /* Tokens compartilhados pertencentes à página */
    :root {
      --scene-accent: #4058b8;
      --scene-rounding: 12px;
    }

    /* Mapeamento de integração pertencente à página */
    orbit-tile {
      --orbit-tile-fill: var(--scene-accent);
      --orbit-tile-radius: var(--scene-rounding);
    }

    /* Um tema local da página */
    .alternate {
      --scene-accent: #276f64;
      --scene-rounding: 24px;
    }

    /* Uma substituição intencional para uma instância do componente */
    orbit-tile.special {
      --orbit-tile-fill: #a65b20;
      --orbit-tile-radius: 50%;
    }

    body {
      max-width: 36rem;
      margin: 3rem auto;
      padding: 0 1rem;
      font: 1rem/1.5 system-ui, sans-serif;
    }
    main { display: grid; gap: 1rem; }
  </style>
</head>
<body>
  <main>
    <orbit-tile>Sample A</orbit-tile>
    <section class="alternate" aria-label="Alternate samples">
      <orbit-tile>Sample B</orbit-tile>
    </section>
    <orbit-tile class="special">Sample C</orbit-tile>
  </main>

  <script>
    class OrbitTile extends HTMLElement {
      constructor() {
        super();
        const root = this.attachShadow({ mode: 'open' });
        const style = document.createElement('style');
        style.textContent = `
          :host { display: block; }
          .tile {
            display: flex;
            align-items: center;
            gap: 1rem;
            padding: 1rem;
            background: #f2f4f6;
            color: #202630;
          }
          .shape {
            display: block;
            width: 4rem;
            height: 4rem;
            flex: 0 0 4rem;
            background-color: var(--orbit-tile-fill, #667085);
            border-radius: var(--orbit-tile-radius, 1rem);
          }
        `;
        const tile = document.createElement('div');
        tile.className = 'tile';
        const shape = document.createElement('span');
        shape.className = 'shape';
        shape.setAttribute('aria-hidden', 'true');
        tile.append(shape, document.createElement('slot'));
        root.append(style, tile);
      }
    }
    customElements.define('orbit-tile', OrbitTile);
  </script>
</body>
</html>

Abra o arquivo salvo no Safari, Chrome ou Firefox. O exemplo não tem scripts externos, fontes, imagens ou folhas de estilo. O JavaScript passou em verificação de sintaxe; os seguintes resultados visuais são resultados esperados derivados das declarações, não um relatório de execução do navegador.

Instância Forma esperada Caminho do valor
Sample A Azul, com cantos 12px Tokens raiz → mapeamento host → componente
Sample B Verde, com cantos 24px Tokens da seção → mapeamento host → componente
Sample C Círculo laranja Substituição da instância → componente

Observe que o JavaScript do componente não contém referências --scene-*. Sua folha de estilo consome apenas os nomes públicos --orbit-tile-*. A página pode reorganizar seu vocabulário de tema sem alterar o componente.

Onde o mapeamento token-para-componente deve acontecer?

Mapeie valores onde o tema pretendido está disponível. Nesta demonstração, a regra orbit-tile roda em cada host. Isso permite que o segundo host resolva --scene-accent após herdar o valor verde da seção.

Evite mover esse mapeamento para :root sem considerar o tempo de resolução. Se --orbit-tile-fill: var(--scene-accent) for computado lá, descendentes herdam seu valor resolvido. Alterar apenas --scene-accent em uma seção posterior não reconstrói o alias herdado. Propriedades personalizadas resolvem suas referências var() antes que seus valores computados sejam herdados. Módulo CSS Propriedades Personalizadas para Variáveis em Cascata Nível 1

A decisão prática é escopo: use mapeamento no nível do host quando cada instância deve seguir os tokens da página ao seu redor. Se uma região inteira intencionalmente compartilha um valor público do componente, declarar essa variável pública na região pode ser apropriado — mas remova o mapeamento do host para essa variável para que não substitua o valor herdado.

Para usos internos repetidos, um alias de implementação pode manter declarações de fallback juntas. Um nome como --_orbit-tile-fill pode sinalizar uma convenção interna, mas o sublinhado não cria privacidade forçada. Consumo direto, como mostrado no exemplo completo, é mais fácil de seguir quando cada valor é usado uma vez. Estilizando Web Components

Como diagnosticar uma substituição que não funciona?

Use as três instâncias como um pequeno teste de contrato. Altere uma declaração por vez, salve e recarregue. Estas verificações ajudam a distinguir um recurso ausente, um problema de herança e um valor inválido.

  1. Altere o destaque raiz para #7251a1. Espere que apenas Sample A o adote; a seção e a instância têm suas próprias escolhas.
  2. Altere o destaque da seção para #32758a. Espere que Sample B mude sem editar código do componente.
  3. Remova o mapeamento de integração inteiro. Espere que Sample A e Sample B usem o preenchimento cinza do componente e raio 1rem; Sample C mantém suas substituições públicas explícitas.
  4. Restaure o mapeamento antes de continuar outros experimentos.

Se todas as formas desaparecerem, primeiro verifique se o documento está salvo como HTML e seu script está intacto. Se rótulos aparecem mas formas estão sem estilo, inspecione a folha de estilo do componente. Uma folha de estilo do documento sozinha não estiliza descendentes shadow por seletores comuns; a implementação deve anexar seus estilos internos à shadow tree. A MDN documenta tanto estilos embutidos quanto folhas de estilo construídas para esse propósito. Usando Shadow DOM

Se apenas uma substituição de ancestral falhar, procure uma declaração da variável pública diretamente no host. Neste exemplo, o mapeamento de integração é tal declaração. Altere o token da página que o alimenta, substitua a variável pública na instância, ou remova o mapeamento quando o theming público no nível do ancestral for o contrato pretendido.

Se uma forma ficar transparente após atribuir --orbit-tile-fill: 12px, o fallback não a reparará. A variável existe, mas seu valor substituído é inválido para background-color. Essa declaração torna-se inválida no tempo de valor computado; ela não retorna a uma declaração background anterior. Remova a atribuição ou forneça uma cor válida. Módulo CSS Propriedades Personalizadas para Variáveis em Cascata Nível 1

As variáveis públicas devem usar @property?

Registro é opcional para este contrato. @property adiciona restrições de sintaxe, um valor inicial e comportamento de herança explícito. A MDN o marca como Baseline 2024, disponível em navegadores atuais desde julho de 2024, enquanto alerta que navegadores mais antigos podem não ter suporte. O exemplo completo evita tornar o registro um pré-requisito. Regra CSS @property

Se você introduzir registro, coordene-o no nível do documento. A documentação da implementação do Chrome relata que registros dentro de shadow roots são ignorados; seu relatório é datado de agosto de 2024, então trate-o como uma fronteira de interoperabilidade documentada em vez de evidência de um novo teste de navegador. O caminho suportado descrito lá é registro de escopo de documento, com o componente consumindo a propriedade através da fronteira. Nomes CSS definidos pelo autor e Shadow DOM: Em especificação e na prática

Registro também muda comportamento. Definir inherits: false quebraria a herança host-para-forma do exemplo. Uma cor inicial registrada significa que uma propriedade não definida tem essa cor, então um var() consumidor fallback não é mais o padrão de valor ausente. Mantenha inherits: true para este design e escolha deliberadamente qual mecanismo possui o padrão. Regra CSS @property

O contrato final deve permitir que um autor de página explique o caminho de cada valor: uma escolha compartilhada da página, um mapeamento para um controle de componente documentado, e um fallback pertencente ao componente. Mantenha o mapeamento próximo ao seu escopo de tema pretendido, deixe entradas públicas herdadas não definidas internamente, e retenha o exemplo de três instâncias sempre que revisar a API.

Fontes