Seletores :has, :is, :where e @container — Web Academy
Durante mais de duas décadas, os desenvolvedores web enfrentaram limitações estruturais severas: era impossível estilizar um elemento pai com base no que existia dentro dele, criar regras agrupadas inflacionava a especificidade desnecessariamente e as Media Queries enxergavam apenas a tela inteira, ignorando o espaço real de cada componente. As especificações modernas do CSS transformaram esse cenário com quatro ferramentas definitivas: as pseudo-classes funcionais :is(), :where(), o aguardado “seletor de pai” :has() e as Container Queries (@container).
📑 Índice do Conteúdo
1. :is() — Agrupamento Limpo com Tolerância a Falhas
A pseudo-classe funcional :is() recebe uma lista de seletores separados por vírgula e casa com qualquer elemento que satisfaça um dos itens listados, eliminando repetições exaustivas de código.
O Fim da Redundância de Seletores
No CSS tradicional, aplicar um mesmo estilo a links em diferentes seções exigia uma cadeia longa e repetitiva:
/* Forma legada repetitiva */
header a:hover,
nav a:hover,
aside a:hover,
footer a:hover {
color: #00f0ff;
text-decoration: underline;
}
/* Forma moderna e concisa com :is() */
:is(header, nav, aside, footer) a:hover {
color: #00f0ff;
text-decoration: underline;
}
Processamento Tolerante a Erros (Forgiving Selector Parsing)
Em uma lista tradicional como header a, :seletor-desconhecido a, footer a, se o navegador não reconhecer um dos seletores, ele invalida a regra inteira. Já com :is(), o motor do navegador apenas descarta o seletor desconhecido e continua aplicando o estilo a todos os outros seletores válidos da lista.
:is() adota a especificidade do seletor mais específico presente na lista. Se a lista contiver um ID (ex: :is(section, article, #destaque) p), toda a regra terá peso de ID (1, 0, 1), mesmo quando aplicada a uma simples section!
2. :where() — O Seletor de Especificidade Zero (0-0-0)
O :where() possui a mesma sintaxe e o mesmo comportamento tolerante a falhas do :is(), mas com uma característica arquitetural única: sua especificidade é SEMPRE ZERO (0-0-0).
/* Especificidade total calculada: 0-0-0 */
:where(header, nav, aside, footer) a {
color: #94a3b8;
text-decoration: none;
}
Por ter peso zero, qualquer regra declarada posteriormente por um desenvolvedor — até mesmo uma tag simples como a { color: #38bdf8; } — sobrescreverá esse estilo com facilidade, tornando o :where() a ferramenta definitiva para folhas de estilo base, Resets e bibliotecas de Design System.
| Característica | Pseudo-classe :is() | Pseudo-classe :where() |
|---|---|---|
| Sintaxe de Lista | :is(sel1, sel2, ...) |
:where(sel1, sel2, ...) |
| Parsing Tolerante (Forgiving) | Sim (ignora seletores inválidos) | Sim (ignora seletores inválidos) |
| Cálculo de Especificidade | Herda a do argumento mais específico da lista | Sempre Zero absoluto (0, 0, 0) |
| Principal Aplicação | Encurtar regras de produção no dia a dia | CSS Resets, normalize e componentes de base |
3. :has() — A Pseudo-classe Relacional (O Seletor de Pai)
Historicamente conhecido como o “Santo Graal” do desenvolvimento web, o :has() é uma pseudo-classe relacional que avalia condições baseadas em elementos descendentes ou irmãos subsequentes, permitindo finalmente estilizar um elemento ancestral direto ou indireto.
/* Sintaxe Geral: estiliza o ALVO se a CONDIÇÃO for verdadeira */
alvo:has(condicao) {
/* propriedades aplicadas ao alvo */
}
Casos de Uso Reais do :has()
A. Card que Reage à Presença de Mídia
Se o card contiver uma tag <img>, ele assume automaticamente um layout em grid de duas colunas; se não tiver imagem, mantém coluna única:
/* Layout padrão em 1 coluna */
.article-card {
display: flex;
flex-direction: column;
}
/* Se possuir uma imagem interna, muda para 2 colunas */
.article-card:has(img) {
display: grid;
grid-template-columns: 140px 1fr;
gap: 1rem;
}
B. Validação Visual de Formulário sem JavaScript
O contêiner do formulário pode alterar sua borda e sombra se qualquer campo obrigatório estiver inválido:
/* Borda de aviso no formulário inteiro quando houver campos inválidos preenchidos */
.form-box:has(input:invalid:not(:placeholder-shown)) {
border-color: #ef4444;
box-shadow: 0 0 15px rgba(239, 68, 68, 0.2);
}
C. Cartões de Seleção Ativos (Radio/Checkbox)
Ao selecionar um botão de opção, o cartão pai inteiro se ilumina:
/* Destaca o card pai cujo input interno estiver checado */
.pricing-card:has(input[type="radio"]:checked) {
border-color: #00f0ff;
background: rgba(0, 240, 255, 0.05);
box-shadow: 0 10px 25px rgba(0, 240, 255, 0.2);
}
D. Efeito de Foco Contextual na Lista (Dimming de Irmãos)
Reduz a opacidade de todos os itens da lista, exceto o item que está sob o ponteiro do mouse:
/* Quando a lista tem algum li sob hover, esmaece os demais */
.feature-list:has(li:hover) li:not(:hover) {
opacity: 0.35;
transform: scale(0.98);
}
4. @container (Container Queries): Além da Janela do Navegador
Enquanto as Media Queries clássicas (@media) observam unicamente o tamanho da janela global (Viewport), as Container Queries (@container) permitem que um componente consulte a largura do elemento pai direto onde foi inserido.
Isso significa que o mesmo card pode ser posicionado dentro de uma sidebar estreita de 280px ou no meio da página de 900px e se adaptar perfeitamente em ambos os cenários sem precisar de classes auxiliares no HTML.
Como Implementar Container Queries em 2 Etapas
1. Definir o Contêiner de Contexto no Elemento Pai:
.card-slot {
/* Define que este elemento responderá a consultas dimensionais */
container-type: inline-size;
container-name: cardzone; /* Nome opcional para escopo direcionado */
}
2. Escrever a Consulta @container no Componente Filho:
/* Estilo base: Vertical para contêineres estreitos */
.product-card {
display: flex;
flex-direction: column;
padding: 1rem;
}
/* Quando o contêiner cardzone tiver 450px ou mais */
@container cardzone (min-width: 450px) {
.product-card {
flex-direction: row;
align-items: center;
padding: 1.5rem;
}
}
1cqi: 1% da largura do contêiner no eixo de leitura (inline size).1cqw: 1% da largura absoluta do contêiner.1cqh: 1% da altura do contêiner.
5. Laboratório Prático: Card Inteligente com :has(), :where() e @container
Analise o exemplo completo abaixo. Ele utiliza :where() para estilos de base, :has() para iluminar o cartão ao marcar a opção e @container para mudar de layout vertical para horizontal conforme a largura do contêiner:
<div class="demo-wrapper">
<p class="resize-hint">↔ Redimensione a caixa arrastando a borda direita:</p>
<div class="subscription-slot">
<div class="plan-card">
<div class="plan-header">
<label class="plan-label">
<input type="radio" name="plano" value="pro" checked>
<span class="plan-name">Plano Full-Stack</span>
<span class="plan-badge">Recomendado</span>
</label>
<div class="plan-price">R$ 49<small>/mês</small></div>
</div>
<div class="plan-body">
<p>Acesso total aos cursos da Web Academy, suíte de ferramentas e Playground IDE ilimitado.</p>
</div>
<div class="plan-action">
<button type="button" class="btn-enroll">Matricular Agora</button>
</div>
</div>
</div>
</div>
/* 1. Estilos com :where() - Especificidade ZERO para fácil sobrescrita */
:where(.plan-card, .btn-enroll, .plan-price) {
font-family: 'Inter', system-ui, sans-serif;
box-sizing: border-box;
}
/* 2. Contêiner Responsivo de Contexto */
.subscription-slot {
container-type: inline-size;
container-name: planslot;
width: 100%;
max-width: 600px;
min-width: 280px;
resize: horizontal;
overflow: auto;
padding: 1.25rem;
background: #0f172a;
border: 1px dashed #334155;
border-radius: 12px;
}
/* 3. Card Base (Vertical) */
.plan-card {
display: flex;
flex-direction: column;
gap: 1rem;
background: #1e293b;
border: 2px solid #334155;
border-radius: 10px;
padding: 1.25rem;
transition: all 0.3s ease;
}
/* 4. Iluminação com :has() ao checar o radio */
.plan-card:has(input[type="radio"]:checked) {
border-color: #00f0ff;
background: linear-gradient(135deg, rgba(0, 240, 255, 0.06), #1e293b);
box-shadow: 0 10px 25px rgba(0, 240, 255, 0.15);
}
.plan-label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
color: #f8fafc;
font-weight: 700;
}
.plan-badge {
background: #00f0ff;
color: #090d16;
font-size: 0.72rem;
font-weight: 800;
padding: 2px 6px;
border-radius: 12px;
text-transform: uppercase;
}
.plan-price {
color: #38bdf8;
font-size: 1.6rem;
font-weight: 800;
}
.btn-enroll {
width: 100%;
padding: 0.75rem;
background: #00f0ff;
color: #090d16;
border: none;
border-radius: 6px;
font-weight: 700;
cursor: pointer;
transition: filter 0.2s;
}
.btn-enroll:hover {
filter: brightness(1.15);
}
/* 5. Container Query: reorganiza quando o contêiner atingir 460px */
@container planslot (min-width: 460px) {
.plan-card {
display: grid;
grid-template-columns: 1fr auto;
grid-template-areas:
"header price"
"body body"
"action action";
padding: 1.5rem;
}
.plan-header { grid-area: header; }
.plan-price { grid-area: price; text-align: right; }
.plan-body { grid-area: body; }
.plan-action { grid-area: action; }
}
6. Ferramentas Web-ZoneWare para o seu Fluxo de Trabalho
Ao arquitetar regras avançadas de seletores e contêineres, utilize as ferramentas da suíte Web-ZoneWare para validar a sintaxe e garantir arquivos compactos:
- Limpador e Formatador CSS: Indente regras aninhadas e seletores modernos com 2 espaços de padrão sênior.
- Validador de CSS Online: Verifique a integridade sintática e identifique declarações obsoletas.
- Playground Web IDE: Laboratório online em tempo real para testar Container Queries e o seletor
:has()instantaneamente.
7. Links Oficiais de Autoridade
- W3C — CSS Selectors Level 4 (Especificação Oficial)
- W3C — CSS Containment Module Level 3 (Container Queries)
- MDN Web Docs — Pseudo-classe :has()
- MDN Web Docs — Guia Completo de Container Queries
8. Exercício Prático da Lição 47
- Abra o Playground Web IDE clicando no botão de ação da lição.
- Crie uma lista desordenada (
<ul class="menu">) com 4 itens de link. - Utilize
.menu:has(li:hover) li:not(:hover)para aplicar opacidade de0.4aos irmãos inativos quando o mouse estiver sobre qualquer item. - Defina um contêiner com
container-type: inline-sizee aplique uma regra@container (min-width: 400px)que altere a disposição da lista de vertical para horizontal. - Verifique se o código CSS está formatado usando a ferramenta CSS Beautifier.
9. Quiz de Fixação Interativo
1. Qual a diferença fundamental de especificidade entre :is() e :where()?
Resposta Correta: O :is() assume o peso do seletor mais específico presente em seus argumentos, enquanto o :where() tem SEMPRE especificidade zero absoluta (0-0-0), facilitando sobrescritas sem conflito.
2. O que acontece em :is(h1, :pseudo-invalido, h2) se o navegador não reconhecer o segundo seletor?
Resposta Correta: Devido ao Forgiving Selector Parsing, o navegador apenas ignora o seletor desconhecido e continua aplicando os estilos normalmente aos seletores válidos (h1 e h2), ao contrário de seletores tradicionais que descartariam toda a regra.
3. Por que Container Queries (@container) são superiores a Media Queries (@media) em componentes reutilizáveis?
Resposta Correta: Porque as Container Queries medem a largura do elemento pai contêiner imediato do componente, permitindo que ele se adapte de forma autônoma quer esteja numa sidebar estreita ou no corpo principal da página, sem depender da largura global do monitor (Viewport).