Um guia para seletores de pseudo-classe CSS modernos e recentemente suportados
Publicados: 2022-03-10 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.
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 daborder-color
e ocultando ooutline
na entrada do teclado -
button
Não só usa:focus-visible
sem a regra extra parabutton:focus:not(:focus-visible)
que remove o contorno em:focus
, mas permitirá a visibilidade dabox-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:
: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
:
: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á aarticle
ep
. - 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:
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
.
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.