Um guia para seletores de pseudo-classe CSS modernos e recentemente suportados

Publicados: 2022-03-10
Resumo rápido ↬ O Rascunho do Editor do Grupo de Trabalho CSS para Seletores Nível 4 inclui vários seletores de pseudo-classe que já possuem candidatos a propostas na maioria dos navegadores modernos. Este guia abordará aqueles que atualmente têm o melhor suporte, juntamente com exemplos para demonstrar como você pode começar a usá-los hoje!

Seletores de pseudo-classe são aqueles que começam com o caractere de dois pontos “ : ” e combinam com base em um estado do elemento atual. O estado pode ser relativo à árvore do documento ou em resposta a uma mudança de estado como :hover ou :checked .

:any-link

Embora definida em Selectors Level 4, esta pseudo-classe tem suporte cross-browser há algum tempo. A any-link corresponderá a um hiperlink âncora desde que tenha um href . Ele corresponderá de maneira equivalente a combinar :link e :visited de uma só vez. Essencialmente, isso pode reduzir seus estilos em um seletor se você estiver adicionando propriedades básicas, como a color , que deseja aplicar a todos os links, independentemente do status visitado.

 :any-link { color: blue; text-underline-offset: 0.05em; }

Uma observação importante sobre a especificidade é que :any-link vencerá contra a como seletor, mesmo que a seja colocado mais baixo na cascata, pois tem a especificidade de uma classe. No exemplo a seguir, os links serão roxos:

 :any-link { color: purple; } a { color: red; }

Portanto, se você introduzir :any-link , esteja ciente de que precisará incluí-lo em instâncias de a como seletor se elas estiverem em competição direta por especificidade.

:focus-visible

Aposto que uma das violações de acessibilidade mais comuns na web é remover o outline de elementos interativos como links, botões e entradas de formulário para seu estado :focus . Um dos principais objetivos desse outline é servir como um indicador visual para usuários que usam principalmente teclados para navegar. Um estado de foco visível é fundamental como uma ferramenta de localização, pois esses usuários navegam em uma interface e ajudam a reforçar o que é um elemento interativo. Especificamente, o foco visível é abordado no Critério de Sucesso 2.4.11 das WCAG: Aparência do foco (mínimo).

A pseudo-classe :focus-visible destina-se a mostrar apenas um anel de foco quando o agente do usuário determina por meio de heurística que ele deve ser visível. Dito de outra forma: os navegadores determinarão quando aplicar :focus-visible com base em coisas como método de entrada, tipo de elemento e contexto da interação. Para fins de teste por meio de um computador desktop com entrada de teclado e mouse, você deve ver os estilos :focus-visible anexados ao tabular em um elemento interativo, mas não ao clicar nele, com exceção de entradas de texto e áreas de texto que devem mostrar :focus-visible para todos os tipos de entrada de foco.

Nota : Para mais detalhes, revise o rascunho de trabalho da especificação :focus-visible .

As versões mais recentes dos navegadores Firefox e Chromium parecem estar lidando com :focus-visible em entradas de formulário de acordo com a especificação que diz que o UA deve remover :focus estilos quando :focus-visible corresponder. O Safari ainda não oferece suporte a :focus-visible , portanto, precisamos garantir que um estilo :focus seja incluído como substituto para evitar a remoção do outline para acessibilidade.

Mais depois do salto! Continue lendo abaixo ↓

Dado um botão e entrada de texto com o seguinte conjunto de estilos, vamos ver o que acontece:

 input:focus, button:focus { outline: 2px solid blue; outline-offset: 0.25em; } input:focus-visible { outline: 2px solid transparent; border-color: blue; } button:focus:not(:focus-visible) { outline: none; } button:focus-visible { outline: 2px solid transparent; box-shadow: 0 0 0 2px #fff, 0 0 0 4px blue; }

Chrome e Firefox

  • input
    Remova corretamente os estilos :focus quando os elementos são focados via entrada do mouse em favor de :focus-visible resultando na alteração da border-color e ocultando o outline na entrada do teclado
  • button
    Não só usa :focus-visible sem a regra extra para button:focus:not(:focus-visible) que remove o contorno em :focus , mas permitirá a visibilidade da box-shadow apenas na entrada do teclado

Safári

  • input
    Continua usando apenas os estilos :focus
  • button
    Isso parece respeitar parcialmente a intenção de :focus-visible no botão, ocultando os estilos :focus ao clicar, mas ainda mostrando os estilos :focus na interação do teclado

Portanto, por enquanto, a recomendação seria continuar incluindo os estilos :focus e, em seguida, aprimorar progressivamente até usar :focus-visible que o código de demonstração permite. Aqui está um CodePen para você continuar testando:

Veja a caneta [Aplicativo de teste de :focus-visible](https://codepen.io/smashingmag/pen/MWJZbew) de Stephanie Eckles.

Veja o aplicativo Pen Testing de :focus-visible por Stephanie Eckles.

:focus-within

A pseudo-classe :focus-within tem suporte entre todos os navegadores modernos e age quase como um seletor pai, mas apenas para uma condição muito específica. Quando anexado a um elemento recipiente e um elemento filho corresponde a :focus , estilos podem ser adicionados ao elemento recipiente e/ou a qualquer outro elemento dentro do recipiente.

Um aprimoramento prático para usar esse comportamento é estilizar um rótulo de formulário quando a entrada associada estiver em foco. Para que isso funcione, envolvemos o rótulo e a entrada em um contêiner e, em seguida, anexamos :focus-within a esse contêiner, além de selecionar o rótulo:

 .form-group:focus-within label { color: blue; }

Isso faz com que o rótulo fique azul quando a entrada estiver em foco.

Esta demonstração do CodePen também inclui a adição de uma estrutura de tópicos diretamente ao .form-group :

Veja a caneta [Aplicativo de teste de :focus-within](https://codepen.io/smashingmag/pen/xxgmREq) de Stephanie Eckles.

Veja o aplicativo Pen Testing de :focus-within de Stephanie Eckles.

:is()

Também conhecida como pseudo-classe “matches any”, :is() pode pegar uma lista de seletores para tentar comparar. Por exemplo, em vez de listar os estilos de título individualmente, você pode agrupá-los no seletor de :is(h1, h2, h3) .

Alguns comportamentos exclusivos sobre a lista de seletores :is() :

  • Se um seletor listado for inválido, a regra continuará a corresponder aos seletores válidos. Dado :is(-ua-invalid, article, p) a regra corresponderá a article e p .
  • A especificidade calculada será igual à do seletor passado com a especificidade mais alta. Por exemplo, :is(#id, p) terá a especificidade do #id — 1.0.0 — enquanto :is(p, a) terá uma especificidade de 0.0.1.

O primeiro comportamento de ignorar seletores inválidos é um benefício importante. Ao usar outros seletores em um grupo em que um seletor é inválido, o navegador descartará toda a regra. Isso entra em jogo em alguns casos em que os prefixos do fornecedor ainda são necessários, e o agrupamento de seletores prefixados e não prefixados faz com que a regra falhe entre todos os navegadores. Com :is() você pode agrupar esses estilos com segurança e eles serão aplicados quando corresponderem e serão ignorados quando não corresponderem.

Para mim, agrupar estilos de cabeçalho como mencionado anteriormente já é uma grande vitória com este seletor. Também é o tipo de regra que eu me sentiria confortável em usar sem fallback ao aplicar estilos não críticos, como:

 :is(h1, h2, h3) { line-height: 1.2; } :is(h2, h3):not(:first-child) { margin-top: 2em; }

Neste exemplo (que vem dos estilos de documento no meu projeto SmolCSS), ter a maior line-height herdada dos estilos base ou a falta do margin-top não é realmente um problema para navegadores não compatíveis. É simplesmente menos do que ideal. O que você não gostaria de usar :is() ainda seriam estilos de layout críticos , como Grid ou Flex, que controlam significativamente sua interface.

Além disso, quando encadeado a outro seletor, você pode testar se o seletor base corresponde a um seletor descendente dentro de :is() . Por exemplo, a regra a seguir seleciona apenas parágrafos que são descendentes diretos de artigos. O seletor universal está sendo usado como referência ao seletor de base p .

 p:is(article > *)

Para o melhor suporte atual, se você quiser começar a usá-lo, você também vai querer dobrar os estilos incluindo regras duplicadas usando :-webkit-any() e :matches() . Lembre-se de fazer essas regras individuais, ou até mesmo o navegador de suporte irá jogá-lo fora! Em outras palavras, inclua todos os itens a seguir:

 :matches(h1, h2, h3) { } :-webkit-any(h1, h2, h3) { } :is(h1, h2, h3) { }

Vale a pena mencionar neste ponto que, juntamente com os próprios seletores mais recentes, há uma variação atualizada de @supports que é @supports selector . Isso também está disponível como @supports not selector .

Nota : Atualmente (dos navegadores modernos), apenas o Safari não suporta esta regra.

Você pode verificar o suporte a :is() com algo como o seguinte, mas na verdade estaria perdendo o suporte ao Safari, pois o Safari suporta :is() , mas não suporta @supports selector .

 @supports selector(:is(h1)) { :is(h1, h2, h3) { line-height: 1.1; } }

:where()

A pseudo-classe :where() é quase idêntica a :is() exceto por uma diferença crítica: ela sempre terá especificidade zero. Isso tem implicações incríveis para pessoas que estão construindo estruturas, temas e sistemas de design . Usando :where() , um autor pode definir padrões e desenvolvedores downstream podem incluir substituições ou extensões sem conflito de especificidade.

Considere o seguinte conjunto de estilos img . Usando :where() , mesmo com um seletor de especificidade mais alto, a especificidade permanece zero. No exemplo a seguir, qual borda de cor você acha que a imagem terá?

 :where(article img:not(:first-child)) { border: 5px solid red; } :where(article) img { border: 5px solid green; } img { border: 5px solid orange; }

A primeira regra tem especificidade zero, pois está totalmente contida em :where() . Então, diretamente contra a segunda regra, a segunda regra vence. Apresentando o seletor somente de elemento img como última regra, ele vai ganhar devido à cascata. Isso ocorre porque ele calculará com a mesma especificidade que a regra :where(article) img , pois a parte :where() não aumenta a especificidade.

Usar :where() junto com fallbacks é um pouco mais difícil devido ao recurso de especificidade zero, já que esse recurso é provavelmente o motivo pelo qual você deseja usá-lo em :is() . E se você adicionar regras de fallback, elas provavelmente vencerão :where() devido à sua própria natureza. E ele tem um suporte geral melhor do que o @supports selector então tentar usar isso para criar um fallback provavelmente não fornecerá muito (se algum) ganho. Basicamente, esteja ciente da incapacidade de criar corretamente fallbacks para :where() e verifique cuidadosamente seus próprios dados para determinar se é seguro começar a usar para seu público-alvo exclusivo.

Você pode testar :where() com o seguinte CodePen que usa os seletores img acima:

Veja a caneta [Testando a especificidade `:where()`](https://codepen.io/smashingmag/pen/jOyXVMg) por Stephanie Eckles.

Veja a especificidade Pen Testing :where() por Stephanie Eckles.

Aprimorado :not()

O seletor básico :not() é suportado desde o Internet Explorer 9. Mas os Seletores Nível 4 aprimoram :not() permitindo que ele receba uma lista de seletores, assim como :is() e :where() .

As regras a seguir fornecem o mesmo resultado no suporte a navegadores:

 article :not(h2):not(h3):not(h4) { margin-bottom: 1.5em; } article :not(h2, h3, h4) { margin-bottom: 1.5em; }

A capacidade de :not() de aceitar uma lista de seletores tem um ótimo suporte a navegadores modernos.

Como vimos com :is() , :not() aprimorado também pode conter uma referência ao seletor base como um descendente usando * . Este CodePen demonstra essa capacidade selecionando links que não são descendentes de nav .

Veja a caneta [Testing :not() with a descendente selector](https://codepen.io/smashingmag/pen/BapvQQv) de Stephanie Eckles.

Veja o Pen Testing :not() com um seletor descendente de Stephanie Eckles.

Bônus : A demonstração anterior também inclui um exemplo de encadeamento :not() e :is() para selecionar imagens que não são irmãos adjacentes de elementos h2 ou h3 .

Proposta mas “em risco” — :has()

A pseudo-classe final que é uma proposta muito empolgante, mas não tem nenhum navegador atual implementando-a, mesmo de maneira experimental, é :has() . De fato, ele está listado no Selector Level 4 Editor's Draft como “em risco”, o que significa que é reconhecido que tem dificuldades em concluir sua implementação e, portanto, pode ser descartado da recomendação.

Se implementado, :has() seria essencialmente o “seletor pai” que muitas pessoas de CSS desejam ter disponível. Ele funcionaria com uma lógica semelhante a uma combinação de :focus-within e :is() com seletores descendentes, onde você está procurando a presença de descendentes , mas o estilo aplicado seria para o elemento pai.

Dada a regra a seguir, se a navegação contivesse um botão, a navegação diminuiria o preenchimento superior e inferior:

 nav { padding: 0.75rem 0.25rem; nav:has(button) { padding-top: 0.25rem; padding-bottom: 0.25rem; }

Novamente, isso não está implementado em nenhum navegador, mesmo experimentalmente - mas é divertido pensar nisso! Robin Rendle forneceu informações adicionais sobre esse futuro seletor em CSS-Tricks.

Menção Honrosa do Nível 3: :empty

Uma pseudo-classe útil que você pode ter perdido dos Seletores de Nível 3 é :empty , que corresponde a um elemento quando não possui elementos filhos, incluindo nós de texto.

A regra p:empty corresponderá a <p></p> mas não a <p>Hello</p> .

Uma maneira de usar :empty é ocultar elementos que talvez sejam espaços reservados para conteúdo dinâmico preenchido com JavaScript. Talvez você tenha um div que receberá os resultados da pesquisa e, quando preenchido, terá uma borda e algum preenchimento. Mas sem resultados ainda, você não quer que ocupe espaço na página. Usando :empty você pode escondê-lo com:

 .search-results:empty { display: none; }

Você pode estar pensando em adicionar uma mensagem no estado vazio e ficar tentado a adicioná-la com um pseudoelemento e content . A armadilha aqui é que as mensagens podem não estar disponíveis para usuários de tecnologia assistiva que são inconsistentes quanto ao acesso ao content . Em outras palavras, para garantir que um tipo de mensagem “sem resultados” seja acessível , você deve adicioná-lo como um elemento real como um parágrafo (um aria-label não seria mais acessível para um div oculto).

Recursos para aprender sobre seletores

CSS tem muito mais seletores incluindo pseudo-classes. Aqui estão mais alguns lugares para saber mais sobre o que está disponível:

  • A documentação dos seletores de CSS do MDN inclui uma lista abrangente de categorias;
  • Eu escrevi um guia de duas partes para seletores avançados de CSS, você pode começar com a primeira parte;
  • Divirta-se aprendendo sobre seletores CSS com o jogo CSS Diner;
  • Kitty Giraudel criou uma ferramenta de explicação do seletor que decompõe e descreve partes de um seletor fornecido.