Como construir um componente de filtragem em CSS puro (2 métodos)

No tutorial de hoje, aprenderemos como construir um componente de filtragem somente CSS, algo que você seria perdoado por pensar que precisa de JavaScript. Usaremos algumas marcações simples, alguns controles de formulário e alguns seletores CSS realmente interessantes que você talvez não tenha usado antes.

No que estamos trabalhando

Cada instrutor aqui no Tuts+ tem sua própria página de arquivo. Vamos recriar uma lista de tutoriais como esta, usando nossa própria marcação. Em seguida, implementaremos um componente que filtrará as postagens com base nas categorias às quais pertencem.

Aqui está nosso projeto final:

Este tutorial foi atualizado com outra implementação que usa o novo :has() seletor ⬇️

Vamos construir!

1. Comece com a marcação HTML

Começamos identificando as categorias de filtros em nosso componente. Neste exemplo, usaremos sete filtros:

  1. All
  2. CSS
  3. JavaScript
  4. jQuery
  5. WordPress
  6. Slider
  7. fullPage.js

Para fazer isso, primeiro definimos sete botões de opção que agrupamos sob o categories palavra-chave. Por padrão, o primeiro botão de opção está marcado:

1
 type="radio" id="All" name="categories" value="All" checked>
2
 type="radio" id="CSS" name="categories" value="CSS">
3
 type="radio" id="JavaScript" name="categories" value="JavaScript">
4
 type="radio" id="jQuery" name="categories" value="jQuery">
5
 type="radio" id="WordPress" name="categories" value="WordPress">
6
 type="radio" id="Slider" name="categories" value="Slider">
7
 type="radio" id="fullPage.js" name="categories" value="fullPage.js">

Em seguida, criamos uma lista ordenada que contém os rótulos relacionados aos botões de opção mencionados.

Lembre-se de que associamos um botão de opção a um rótulo definindo seu id valor igual ao da etiqueta for valor:

1
 class="filters">
2
  
  • 3
         for="All">All
    
    4
      
    
    5
      
  • 6
         for="CSS">CSS
    
    7
      
    
    8
      
  • 9
         for="JavaScript">JavaScript
    
    10
      
    
    11
      
  • 12
         for="jQuery">jQuery
    
    13
      
    
    14
      
  • 15
         for="WordPress">WordPress
    
    16
      
    
    17
      
  • 18
         for="Slider">Slider
    
    19
      
    
    20
      
  • 21
         for="fullPage.js">fullPage.js
    
    22
      
    
    23
    
    

    A seguir, montamos outra lista ordenada que inclui os elementos que queremos filtrar (nossos cartões). Cada um dos elementos filtrados terá um personalizado data-category atributo cujo valor é uma lista de filtros separados por espaços em branco:

    1
     class="posts">
    
    2
      class="post" data-category="CSS JavaScript">...
    
    3
      class="post" data-category="CSS JavaScript">...
    
    4
     
    
    5
     
    
    6
    
    

    No nosso caso, os elementos filtrados serão postagens. Portanto, a marcação que usaremos para descrever uma postagem junto com sua meta (título, imagem, categorias) fica assim:

    1
    2
      
    3
         href="" target="_blank">
    
    4
           src="IMG_SRC" alt="">
    
    5
        
    
    6
        
    7
           class="post-categories">
    
    8
            
  • 9
               href="">...
    
    10
            
  • 11
            
    
    12
            
    
    13
          
    
    14
           class="post-title">
    
    15
             href="" target="_blank">...
    
    16
          
    
    17
        
    
    18
      
    
    19
    
    

    Com a marcação pronta, vamos voltar nossa atenção para os estilos necessários.

    2. Defina os estilos

    Primeiro ocultamos visualmente os botões de opção:

    1
    input[type="radio"] {
    
    2
      position: absolute;
    
    3
      left: -9999px;
    
    4
    }
    

    Em seguida, adicionamos alguns estilos aos filtros:

    1
    :root {
    
    2
      --black: #1a1a1a;
    
    3
      --white: #fff;
    
    4
      --green: #49b293;
    
    5
    }
    
    6
    
    
    7
    .filters {
    
    8
      text-align: center;
    
    9
      margin-bottom: 2rem;
    
    10
    }
    
    11
    
    
    12
    .filters * {
    
    13
      display: inline-block;
    
    14
    }
    
    15
    
    
    16
    .filters label {
    
    17
      padding: 0.5rem 1rem;
    
    18
      margin-bottom: 0.25rem;
    
    19
      border-radius: 2rem;
    
    20
      min-width: 50px;
    
    21
      line-height: normal;
    
    22
      cursor: pointer;
    
    23
      transition: all 0.1s;
    
    24
    }
    
    25
    
    
    26
    .filters label:hover {
    
    27
      background: var(--green);
    
    28
      color: var(--white);
    
    29
    }
    

    Layout de grade CSS

    Continuamos especificando alguns estilos para os elementos filtrados. Mais importante ainda, usamos CSS Grid para organizá-los de forma diferente dependendo do tamanho da tela:

    1
    :root {
    
    2
      --black: #1a1a1a;
    
    3
      --white: #fff;
    
    4
      --green: #49b293;
    
    5
    }
    
    6
    
    
    7
    .posts {
    
    8
      display: grid;
    
    9
      grid-gap: 1.5rem;
    
    10
      grid-template-columns: repeat(4, 1fr);
    
    11
    }
    
    12
    
    
    13
    .posts .post {
    
    14
      background: #fafafa;
    
    15
      border: 1px solid rgba(0, 0, 0, 0.1);
    
    16
    }
    
    17
    
    
    18
    .posts .post-title {
    
    19
      font-size: 1.3rem;
    
    20
    }
    
    21
    
    
    22
    .posts .post-title:hover {
    
    23
      text-decoration: underline;
    
    24
    }
    
    25
    
    
    26
    .posts figcaption {
    
    27
      padding: 1rem;
    
    28
    }
    
    29
    
    
    30
    .posts .post-categories {
    
    31
      margin-bottom: 0.75rem;
    
    32
      font-size: .75rem;
    
    33
    }
    
    34
    
    
    35
    .posts .post-categories * {
    
    36
      display: inline-block;
    
    37
    }
    
    38
    
    
    39
    .posts .post-categories li {
    
    40
      margin-bottom: 0.2rem;
    
    41
    }
    
    42
    
    
    43
    .posts .post-categories a {
    
    44
      padding: 0.2rem 0.5rem;
    
    45
      border-radius: 1rem;
    
    46
      border: 1px solid;
    
    47
      line-height: normal;
    
    48
      background: all 0.1s;
    
    49
    }
    
    50
    
    
    51
    .posts .post-categories a:hover {
    
    52
      background: var(--green);
    
    53
      color: var(--white);
    
    54
    }
    
    55
    
    
    56
    @media screen and (max-width: 900px) {
    
    57
      .posts {
    
    58
        grid-template-columns: repeat(3, 1fr);
    
    59
      }
    
    60
    }
    
    61
    
    
    62
    @media screen and (max-width: 650px) {
    
    63
      .posts {
    
    64
        grid-template-columns: repeat(2, 1fr);
    
    65
      }
    
    66
    }
    

    Observação: Por motivos de legibilidade, em nosso CSS não agrupamos regras CSS comuns.

    Adicionando os estilos de filtragem

    A ideia aqui é surpreendentemente simples. Cada vez que clicamos em um filtro, apenas os elementos filtrados correspondentes (posts) deverão aparecer. Para implementar essa funcionalidade, usaremos uma combinação dos seguintes recursos CSS:

    Quando clicamos no All filtrar, todas as postagens que possuem um data-category atributo aparecerá:

    1
    [value="All"]:checked ~ .posts [data-category] {
    
    2
      display: block;
    
    3
    }
    

    Quando clicamos em qualquer outra categoria de filtro, apenas as postagens alvo ficarão visíveis:

    1
    [value="CSS"]:checked ~ .posts .post:not([data-category~="CSS"]),
    
    2
    [value="JavaScript"]:checked ~ .posts .post:not([data-category~="JavaScript"]),
    
    3
    [value="jQuery"]:checked ~ .posts .post:not([data-category~="jQuery"]),
    
    4
    [value="WordPress"]:checked ~ .posts .post:not([data-category~="WordPress"]),
    
    5
    [value="Slider"]:checked ~ .posts .post:not([data-category~="Slider"]),
    
    6
    [value="fullPage.js"]:checked ~ .posts .post:not([data-category~="fullPage.js"]) {
    
    7
      display: none;
    
    8
    }
    

    Por exemplo, desde que cliquemos no Slider categoria de filtro, apenas as postagens que pertencem à Slider a categoria ficará visível.

    Componente de filtragem somente CSS em açãoComponente de filtragem somente CSS em açãoComponente de filtragem somente CSS em ação
    Componente de filtragem somente CSS em ação

    Vale ressaltar que em nossos estilos acima ao invés do [att~=val] sintaxe, poderíamos igualmente ter usado a [att*=val] sintaxe. Esta é a aparência dessa mudança sutil:

    1
    [value="CSS"]:checked ~ .posts .post:not([data-category*="CSS"]),
    
    2
    [value="JavaScript"]:checked ~ .posts .post:not([data-category*="JavaScript"]),
    
    3
    [value="jQuery"]:checked ~ .posts .post:not([data-category*="jQuery"]),
    
    4
    [value="WordPress"]:checked ~ .posts .post:not([data-category*="WordPress"]),
    
    5
    [value="Slider"]:checked ~ .posts .post:not([data-category*="Slider"]),
    
    6
    [value="fullPage.js"]:checked ~ .posts .post:not([data-category*="fullPage.js"]) {
    
    7
      display: none;
    
    8
    }
    

    Explicação rápida do seletor CSS

    O que exatamente esse seletor está dizendo?

    A primeira parte [value="CSS"]:checked procura por botões de opção marcados com um valor específico (“CSS” neste caso).

    Depois disso, o til (~) é o que hoje chamamos de “seletor de irmão subsequente”. Ele seleciona elementos que possuem o mesmo pai do elemento anterior, mesmo que eles não sigam imediatamente na marcação. Então ~ .posts .post procura o elemento .posts .post que compartilha o mesmo pai que a entrada de rádio verificada.

    Para ser ainda mais específico, :not([data-category~="CSS"]) refina nosso seletor para apenas aqueles .post elementos que fazem não tenha um data-category atributo que contém um valor de CSS em algum lugar dentro de uma lista separada por espaço.

    Em seguida, aplica-se uma display: none; a quaisquer elementos que correspondam a esses critérios.

    É um seletor bastante complexo, embora seja perfeitamente lógico. Em termos de linguagem humana, você pode descrevê-lo como:

    “Quando o rádio com valor “CSS” for verificado, encontre quaisquer elementos irmãos subsequentes que não contenham “CSS” em sua lista de categorias de dados e oculte-os.”

    Última parte do estilo

    Como última etapa, adicionamos uma regra que destaca a categoria do filtro ativo:

    1
    :root {
    
    2
      --black: #1a1a1a;
    
    3
      --white: #fff;
    
    4
      --green: #49b293;
    
    5
    }
    
    6
    
    
    7
    [value="All"]:checked ~ .filters [for="All"],
    
    8
    [value="CSS"]:checked ~ .filters [for="CSS"],
    
    9
    [value="JavaScript"]:checked ~ .filters [for="JavaScript"],
    
    10
    [value="jQuery"]:checked ~ .filters [for="jQuery"],
    
    11
    [value="WordPress"]:checked ~ .filters [for="WordPress"],
    
    12
    [value="Slider"]:checked ~ .filters [for="Slider"],
    
    13
    [value="fullPage.js"]:checked ~ .filters [for="fullPage.js"] {
    
    14
      background: var(--green);
    
    15
      color: var(--white);
    
    16
    }
    

    3. Acessibilidade

    Essa filtragem é facilmente acessível por padrão; graças à forma nativa como os botões de opção e rótulos funcionam, podemos filtrar nossos itens com as teclas do teclado. Primeiro pressione o Guia para mover o foco para o botão de opção marcado. Em seguida pressione o Teclas de seta para mover o foco e a seleção para os outros botões de opção. Experimente você mesmo:

    Dito isto, não prestámos muita atenção à acessibilidade, por isso pode haver outros aspectos que precisam de ser melhorados.

    4. Extra: Filtrando com o :has() Seletor

    Vamos agora reconstruir este componente de alternância somente CSS aproveitando as vantagens do moderno :has() Pseudoclasse CSS. Este seletor flexível nos permite estilizar elementos que atendam às condições definidas dentro do :has() função. Neste caso, não usaremos o :not() e ~ Seletores CSS.

    Primeiro, ocultamos todas as postagens:

    1
    .posts .post {
    
    2
      display: none;
    
    3
    }
    

    Depois disso, substituímos as regras de filtragem por estas:

    1
    /*CUSTOM VARIABLES HERE*/
    
    2
    
    
    3
    *:has([value="All"]:checked) .filters [for="All"],
    
    4
    *:has([value="CSS"]:checked) .filters [for="CSS"],
    
    5
    *:has([value="JavaScript"]:checked) .filters [for="JavaScript"],
    
    6
    *:has([value="jQuery"]:checked) .filters [for="jQuery"],
    
    7
    *:has([value="WordPress"]:checked) .filters [for="WordPress"],
    
    8
    *:has([value="Slider"]:checked) .filters [for="Slider"],
    
    9
    *:has([value="fullPage.js"]:checked) .filters [for="fullPage.js"] {
    
    10
      background: var(--green);
    
    11
      color: var(--white);
    
    12
    }
    
    13
    
    
    14
    *:has([value="All"]:checked) .posts [data-category],
    
    15
    *:has([value="CSS"]:checked) .posts [data-category~="CSS"],
    
    16
    *:has([value="JavaScript"]:checked) .posts [data-category~="JavaScript"],
    
    17
    *:has([value="jQuery"]:checked) .posts [data-category~="jQuery"],
    
    18
    *:has([value="WordPress"]:checked) .posts [data-category~="WordPress"],
    
    19
    *:has([value="Slider"]:checked) .posts [data-category~="Slider"],
    
    20
    *:has([value="fullPage.js"]:checked) .posts [data-category~="fullPage.js"] {
    
    21
      display: block;
    
    22
    }
    

    Vamos isolar um deles e traduzi-lo em termos da linguagem humana.

    Considere este:

    1
    *:has([value="CSS"]:checked) .posts [data-category~="CSS"] {
    
    2
      display: block;
    
    3
    }
    

    Podemos descrevê-lo assim:

    Verifique se existe algum seletor (pai) que inclua um botão de opção marcado com o valor “CSS”. Se for esse o caso, encontre todos os posts descendentes cujo atributo “categoria de dados” contém a palavra-chave “CSS” e exiba-os.

    Ou assim:

    Combine e exiba as postagens cujo atributo “categoria de dados” contém a palavra-chave “CSS” e são filhos de qualquer elemento pai que inclua um botão de opção marcado com o valor “CSS”.

    Aqui está a demonstração resultante, como lembrete:

    Conclusão

    É isso aí, pessoal! Com apenas algumas regras CSS e alguma marcação estruturada, conseguimos construir duas variações de um componente de filtragem baseado em CSS totalmente funcional.

    Espero que você tenha gostado deste exercício e que ele tenha ajudado a expandir sua compreensão sobre seletores CSS.

    Como sempre, obrigado pela leitura!

    Mais sobre seletores CSS

    Deixe uma resposta