Os componentes da Web são elementos personalizados, Shadow DOM e modelos HTML: as primitivas da plataforma para componentes reutilizáveis e independentes de framework, com marcação e estilos encapsulados do documento. Use-os para sistemas de design, widgets incorporáveis e interface independente.
As regras abaixo são transversais. Recupere o guia específico para a tarefa em questão. Cada um deles reafirma o contexto necessário e documenta as APIs e as armadilhas exclusivas.
Decida se você realmente precisa de um elemento personalizado
Use um elemento personalizado somente quando precisar de comportamento encapsulado, ciclo de vida ou uma API reutilizável com suporte a JS em uma tag. Não crie um quando:
- Um elemento HTML simples com CSS seria suficiente. Um
<button>ou<details>estilizado é mais acessível e mais barato do que uma reimplementação. - O elemento é puramente de apresentação, sem comportamento. Uma classe em um elemento nativo tem melhor desempenho e pode ser definida com
@layerou@scope. - Você só precisa de escopo de estilo no servidor sem interatividade. Uma folha de estilo com escopo ou um DOM paralelo declarativo (sem upgrade) pode ser suficiente.
A recriação da semântica nativa (um <my-button> personalizado que recria o foco, a ativação e o ARIA) é o uso indevido mais comum: você herda a acessibilidade da plataforma sem custo financeiro apenas se reutilizar o elemento nativo. Quando um wrapper no estilo <my-button> é garantido (geralmente para hooks de estilo compartilhados, marcação extra ou comportamento que um elemento nativo não pode expressar), envolva o <button> real em vez de reimplementá-lo (consulte Estilização de componentes da Web para encaminhar estilos para um elemento nativo interno).
O teste é se você precisa de um estado encapsulado, uma API programável ou um comportamento que o elemento nativo não pode expressar, não apenas uma aparência diferente. Um botão de curtir/favoritar se qualifica: ele carrega o estado de domínio travado, expõe esse estado como um gancho de estilo para a página ao redor e envia o próprio evento change, algo que um <button> estilizado não oferece. Um botão que só muda de cor quando pressionado não faz isso: use <button> + CSS. Ao criá-lo, modele o estado com um estado ou atributo personalizado, conforme apropriado (consulte Elementos personalizados).
Combine a ferramenta com a base de código
Se algo deve ser um elemento personalizado depende do que o projeto já usa. Os agentes tendem a abstrair demais e buscar um novo elemento com muita pressa. Em caso de dúvida, a opção mais leve (elemento nativo, CSS, um modelo de servidor) geralmente é a certa, e você pode promover para um elemento personalizado mais tarde se a reutilização ou o comportamento realmente exigirem isso. Heurística, em prioridade aproximada:
- A base de código já tem um modelo de componente (React, Vue, Svelte etc.). Use o componente desse framework como padrão para a interface no app. Use um elemento personalizado apenas quando o item precisar escapar ou sobreviver ao framework: uma primitiva de sistema de design compartilhada entre apps ou frameworks, um widget inserido em uma marcação que o framework não controla ou uma saída que precisa funcionar como HTML simples (e-mail, CMS, fragmentos de SSR). Não use um sistema de componentes paralelo para vistas de app comuns.
- Sem ferramentas, marcação estática na maior parte. Prefira HTML + CSS simples ou um modelo/parcial de servidor. Um elemento personalizado só é útil quando você realmente precisa de comportamento, ciclo de vida ou uma API com suporte de JS na tag, não apenas para compartilhar marcação, o que uma inclusão de servidor ou
<template>faz sem enviar nenhum JS. - Gerador de sites estáticos com ilhas de interatividade. Renderize a marcação de forma estática e reserve o elemento personalizado para a parte interativa. Para "muita marcação, pouca interatividade", mantenha a marcação no Light DOM (ou Declarative Shadow DOM) e deixe que um pequeno elemento personalizado a aprimore progressivamente, em vez de renderizar tudo de novo do JS.
- Em vez disso, você pode ampliar um elemento integrado. Usar um elemento nativo até o limite (CSS, alguns listeners, um pseudoelemento ocasional) geralmente é preferível a um elemento personalizado, porque você mantém a acessibilidade e o comportamento da plataforma sem custo financeiro. Mude para um elemento personalizado quando as soluções alternativas não forem mais sustentáveis, ou seja, quando você estiver lutando contra o comportamento integrado do elemento ou quando a experiência de criação se tornar pior do que uma API de componente limpa. Precisar de um pouco de JS em um elemento integrado não é, por si só, um motivo para substituí-lo.
Regras transversais
Elas são válidas para todos os guias abaixo:
- OBRIGATÓRIO: o nome da tag precisa estar em kebab-case com pelo menos um hífen (por exemplo,
my-element). É isso que diferencia os elementos personalizados dos elementos integrados atuais e futuros. - OBRIGATÓRIO: chame
super()primeiro no construtor, antes de tocar emthis. Chamar depois de qualquer instrução que leia ou gravethisgera um erro. - OBRIGATÓRIO: use elementos semânticos (
<button>,<nav>,<ul>) dentro de modelos de sombra. A maioria das falhas de acessibilidade em árvores de sombra vem de marcações não semânticas. Um slot não muda o papel do que ele projeta. - OBRIGATÓRIO: as referências de
iddo ARIA (aria-labelledby,aria-describedby,aria-controls) não podem cruzar um limite de sombra. O elemento de referência e oidpara o qual ele aponta precisam estar na mesma árvore. - Prefira
attachShadow({ mode: 'open' })para que as ferramentas de desenvolvimento, os scripts de página e a hidratação do servidor possam acessarelement.shadowRoot. Use'closed'somente quando precisar ocultar a estrutura interna dos scripts de página. - Evite elementos integrados personalizados (
extends HTMLButtonElementcomis="…"). O Safari se recusou permanentemente a enviá-los.
Matriz do guia de casos de uso
Identifique a tarefa e recupere o guia dela:
| Se você precisar… | Recuperar |
|---|---|
| Definir uma nova tag semântica com comportamento e ciclo de vida | Elementos personalizados |
| Definir o escopo de estilos e marcação para um componente e projetar o conteúdo do consumidor nele | Shadow DOM, modelos e slots |
| Renderizar a árvore de sombra de um componente no servidor (SSR) sem um flash de conteúdo sem estilo | Pré-renderização de elementos personalizados |
Estilo no limite da sombra (temas, ::part, propriedades personalizadas) |
Estilização de componentes da Web |
Enviar o valor de um controle personalizado para um <form> ao redor |
Elementos personalizados associados a formulários |
| Tornar um elemento personalizado acessível (papéis, foco, ARIA em raízes de sombra) | Componentes da Web acessíveis |