Artigo original: How to create responsive tables with pure CSS using Grid Layout Module

Versão resumida

A maneira mais popular de exibir uma coleção de dados semelhantes é pelo uso de tabelas, mas a tabela do HTML têm a desvantagem de ser difícil de tornar responsivas.

Neste artigo, eu uso o Grid Layout do CSS e suas propriedades (sem usar o JavaScript) para criar tabelas que encapsulam colunas, dependendo da largura da tela, com ainda mais mudanças para um card baseado em layout para telas pequenas.

Para os impacientes, vejam no Codepen a seguir a implementação de um exemplo.

Uma pequena história sobre as tabelas responsivas do HTML

Tabelas responsivas não são um novo tópico. Muitas soluções já foram propostas. Responsive Data Table Roundup (Resumo sobre tabelas responsivas de dados, em português), publicado pela primeira vez em 2012 por Chris Coyier, faz um bom resumo de modo muito claro (incluindo uma atualização de 2018).

Really Responsive Tables using CSS3 Flexbox (Tabelas realmentes responsivas usando CSS3 Flexbox, em português) por Vasan Subramanian, mostra uma ideia de agrupar colunas, implementada com o Flexbox.

Embora muitas ideias interessantes tenham sido propostas, bibliotecas como  o Bootstrap optam pela rolagem horizontal para telas pequenas.

Como agora temos o CSS Grid, penso que poderíamos ter uma alternativa comum para uma melhor rolagem horizontal.

Tabelas em HTML

Iniciando pelo básico, uma tabela no HTML é um formato de layout para mostrar uma coleção de itens através de uma matriz de linhas e colunas. Os itens são dispostos em linhas, com os mesmos atributos de dados nas mesmas colunas, com as linhas frequentemente ordenadas com um ou mais atributos ordenáveis. O formato oferece uma visão panorâmica para compreender e examinar rapidamente grandes quantidades de dados.

Por exemplo, aqui está uma tabela hipotética de detalhes do pedido de compra, que você pode ver em uma aplicação de compras.

1_B78yFFUVc1X8uEp_gVLcNw
Detalhes de uma tabela de pedidos de compra

Um item, neste caso, é um detalhe do pedido de compra, que possui atributos como número da peça, descrição da peça etc.

Ao usar tabelas em HTML, o layout dos dados é codificado como linhas e colunas (<tr> e <td>, respectivamente). Isso pode ser suficiente para uso por uma tela que se encaixe em toda a largura da tabela, mas, na realidade, não se aplica aos inúmeros tipos de dispositivos que existem hoje. Em termos de hacks, você pode alterar a propriedade de exibição das tabelas e usar qualquer layout que possa fazer com CSS em geral, mas não parece semanticamente correto.  

Tabelas redefinidas (ou uma coleção de itens)

Vamos iniciar redefinindo como os dados da tabela devem ser expressos em HTML.

Conforme falado anteriormente, como os dados da tabela são essencialmente uma coleção ordenada de itens, parece natural usar listas ordenadas. Além disso, como as tabelas são frequentemente usadas para gerar descrições textuais, parece natural incluir isso uma seção, mas isso pode depender do contexto de como os dados da tabela são usados.

<section>
 <ol>
  <!-- O primeiro item da lista é o cabeçalho da tabela -->
  <li>
   <div>#</div>
   <!-- Envolva atributos semanticamente semelhantes como uma hierarquia de divs -->
   <div>
    <div>Número da peça</div>
    <div>Descrição da peça</div>
   </div>
   ...
  </li>
  <!-- O resto dos itens da lista são os dados de fato -->
  <li>
   <div>1</div>
   <!-- Informações relacionadas ao grupo de peças -->
   <div>
    <div>100-10001</div>
    <div>Descrição das peças</div>
   </div>
  ...
  </li>
 ...
 </ol>
</section>

As <div> "puras" são usadas para expressar atributos de itens, uma vez que o HTML5 não define uma tag apropriada para isso. A chave aqui é expressar semanticamente atributos similares como um hierarquia de <div>. Essa estrutura será usada ao definir como os dados devem ser dispostos. Voltarei nesse ponto na próxima seção de tópicos de estilos.

Quanto aos dados reais dentro do elemento <div>, o primeiro item da lista é o cabeçalho e o restante dos itens são os dados principais.

Agora, é o momento de falar sobre estilizar os itens de estilos com o CSS Grid.

Estilizando coleções de itens

A ideia básica aqui é exibir todos os atributos de itens como uma tabela normal, se a largura de exibição permitir. Esse layout ajuda a poder ver o máximo de itens (linhas) possíveis.

1_6sZipUcqB3hru4Q5r0kORw
Tabela completa

Quando a largura da exibição fica mais estreita, alguns atributos são empilhados verticalmente para economizar espaço horizontal. A escolha dos atributos empilhados deve ser baseada em:

  1. Os atributos fazem sentido quando estão empilhados ?
  2. Quando empilhados verticalmente, o espaço horizontal é preservado?
1_llLsnXzdnBBfMRPqoKNBmw
Agrupando a tabela 1: comece agrupando as colunas que precisam de pouca largura e dê espaço para as outras colunas
1_DdQ-n4VzeGU1EzhRKdHj8w
Agrupando a tabela 2: agrupe "Part Description" (descrição da peça) para poder ver a descrição
1_ys0ukWXXtbWhVyXTD9E0Zw
Agrupando a tabela 3: agrupe ainda mais com "Vendor Name" (nome do fornecedor)
1_-ik1zA0LDXzWib7Ux-4EpQ
Agrupando a tabela 4: envolva as informações relacionadas ao "Vendor" (fornecedor) em informações relacionadas à "Part" (peça)
1_sEvQQjZoux7PEii3JQpCRg
Agrupando a tabela 5: tabela totalmente agrupada

Quando a largura diminui ainda mais para o tamanho de um dispositivo móvel, cada item é exibido como um card. Este layout tem redundância, pois os nomes dos atributos são exibidos repetidamente em cada card e tem a menor capacidade de visualização, mas isso não compromete a usabilidade (por exemplo, rolagem horizontal, texto muito pequeno etc).

1_jI0hhzrpYpjbO3-fGh8IWA
Layout de card de duas colunas
1_XCCcicUngRBcBaKyETC4vg
Layout de card de uma coluna

Agora vamos aos detalhes.

Estilização – passo 1: a tabela inteira

Aqui está um resumo visual de como serão as alterações implementadas com o CSS Grid.

1_uA9PfcQ9JCzY54mH7p_v-A
Contêineres do grid

Para fazer com que as colunas sejam agrupadas, vários grid containers são definidos como uma hierarquia. As caixas vermelhas são um tipo de grid container para cada linha e as caixas azuis são para cada grupo de colunas que é agrupado.

Vamos começar ajustando a lista como um grid container, definindo uma classe chamada .item-container e inserindo <li> (caixa vermelha).

.item-container {
    display: grid;
    grid-template-columns: 2em 2em 10fr 2fr 2fr 2fr 2fr 5em 5em;
}

O número de colunas explícitas especificadas com  grid-template-columns é igual a nove, que é o número de <div> de nível superior, diretamente aninhadas em  <li>.

A largura da coluna é definida em comprimento relativo para fazer com que as colunas sejam agrupadas. A fração real deve ser ajustada com base no conteúdo.

As colunas que não foram encapsuladas são definidas em comprimento absoluto para maximizar o uso de largura para as colunas de quebra automática. No exemplo dos detalhes dos pedidos de compras (purchase order details), a segunda coluna é um ID de dois dígitos, então defino a largura para dobrar esse tamanho de 2 em.    

Em seguida, definimos outro grid container chamado .attribute-container e o aplicamos em todas as <div> intermediárias sob a lista (caixas azuis).

.attribute-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--column-width-min), 1fr));
    }

A largura mínima da coluna para todos os itens do grid em .attribute-container é especificada com variável do CSS chamada --column-width-min  (abordaremos isso mais tarde) usando a função minmax, com o máximo definido para ocupar o resto do espaço (exemplo: uma fração). Como grid-template-columns está definido como repeat, o espaço horizontal disponível será dividido ao número máximo de colunas que possa ocupar, pelo menos, --column-width-min. O restante das colunas vai para a próxima linha. A largura da coluna será esticada se houver excesso de espaço horizontal, pois o repeat está definido como auto-fit.

Estilização – passo 2: agrupando a tabela

Em seguida, --column-width-min precisa ser independentemente especificado para cada coluna, a fim de agrupá-las. Só para ficar claro, as variáveis precisam ser especificadas para que a tabela inteira também seja renderizada corretamente. Para fazer isso, a class é definida para cada .attribute-container. Uma --column-width-min diferente é especificada para cada escopo de class.

Vamos dar uma olhada no HTML, onde .part-id é aplicado,

<div class="attribute-container part-id">
    <div>Part Number</div>
    <div>Part Description</div>
</div>

Vejamos também o CSS:

.part-id {
    --column-width-min: 10em;
}

Esse grid container específico terá duas colunas, contanto que a largura disponível seja maior que 10 em para cada item do grid (exemplo: o grid container é mais largo do que 20 em). Uma vez que a largura do grid container se torne mais estreita do que 20 em, o segundo item grid passará para a próxima linha.

Quando combinamos as propriedades do CSS desse modo, precisaremos somente de um grid container .attribute-container, com os detalhes alterados onde a class é aplicada.

Podemos ainda aninhar .attribute-container, para obter múltiplos níveis de agrupamento com diferentes larguras, como no exercício a seguir:

<div class="attribute-container part-information">
    <div class="attribute-container part-id">
        <div class="attribute" data-name="Part Number">Número de peça</div>
        <div class="attribute" data-name="Part Description">Descrição da peça
    </div>
    </div>
    <div class="attribute-container vendor-information">
        <div class="attribute">Número do fornecedor</div>
        <div class="attribute">Nome do fornecedor</div>
    </div>
</div>
.part-information {
    --column-width-min: 10em;
}
.part-id {
    --column-width-min: 10em;
}
.vendor-information {
    --column-width-min: 8em;
}

Todos os itens acima estão incluídos na media query abaixo. O breakpoint deve ser selecionado com base na largura necessária quando sua tabela é agrupada ao extremo.

@media screen and (min-width: 737px) {
...
}

Estilização – passo 3: layout de cards

O layout de cards será semelhante a um formulário típico, com nomes de atributos  na primeira coluna e valores de atributos na segunda coluna.

Para fazer isso, uma class chamada .attribute é definida e aplicada para todas as tags <div> sob as <li>.

.attribute {
    display: grid;
    grid-template-columns: minmax(9em, 30%) 1fr;
}

Os nomes dos atributos são obtidos de um atributo personalizado das div chamadas data-name, por exemplo  <div class="atributo" data-name="Número da pela"> e um pseudoelemento é criado. O pseudoelemento estará sujeito ao layout do grid container.

.atributo::before {
    content: attr(data-name);
}

O primeiro item da lista é o cabeçalho e não precisa ser mostrado.

/* Não exiba o primeiro item, pois ele é usado para exibir o cabeçalho para layouts tabulares */
.collection-container>li:first-child {
    display: none;
}

Finalmente, os cards são expostos em uma coluna em dispositivos móveis, mas em duas para telas com um pouco mais de largura, mas não o bastante para mostrar uma tabela.

/* 2 Colunas para o Card Layout */
@media screen and (max-width: 736px) {
    .collection-container {
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-gap: 20px;
    }
...
}
/* 1  Coluna para Card Layout */
@media screen and (max-width:580px) {
    .collection-container {
        display: grid;
        grid-template-columns: 1fr;
    }
}

Notas finais

Acessibilidade é uma área que não foi considerada neste artigo e pode haver espaço para melhorias nesse quesito. Obrigado pela leitura.