⚡ Curso de CSS • Web-ZoneWare

Seletores :has, :is, :where e @container — Web Academy

Lição 47 de 50

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).

💡
A Virada de Paradigma: O CSS moderno deixou de ser apenas uma camada de pintura estática ligada ao monitor para se transformar em um motor de arquitetura de componentes modulares, capaz de reagir à estrutura interna do HTML e às dimensões do seu próprio contêiner sem nenhuma linha de JavaScript.

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:

CSS3 14 linhas
/* 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.

⚡
Atenção à Especificidade do :is(): O :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).

CSS3 5 linhas
/* 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.

CSS3 4 linhas
/* 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:

CSS3 12 linhas
/* 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:

CSS3 5 linhas
/* 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:

CSS3 6 linhas
/* 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:

CSS3 5 linhas
/* 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:

CSS3 5 linhas
.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:

CSS3 15 linhas
/* 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;
  }
}
📐
Novas Unidades de Medida Relativas ao Contêiner:

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:

HTML5 24 linhas
<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>
CSS3 98 linhas
/* 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; }
}
🧪 Quer ver este código funcionando agora?

Abrir no Playground IDE →

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:

8. Exercício Prático da Lição 47

  1. Abra o Playground Web IDE clicando no botão de ação da lição.
  2. Crie uma lista desordenada (<ul class="menu">) com 4 itens de link.
  3. Utilize .menu:has(li:hover) li:not(:hover) para aplicar opacidade de 0.4 aos irmãos inativos quando o mouse estiver sobre qualquer item.
  4. Defina um contêiner com container-type: inline-size e aplique uma regra @container (min-width: 400px) que altere a disposição da lista de vertical para horizontal.
  5. 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).


Rolar para cima