Tabelas de dados grandes geralmente ficam ilegíveis em telas pequenas, porque as colunas transbordam ou encolhem além da legibilidade. Este guia demonstra como usar o posicionamento fixo para manter os cabeçalhos visíveis durante a rolagem e como transformar a tabela em um layout "empilhado" compatível com dispositivos móveis quando o espaço é limitado.
Abordagem recomendada
O principal de uma tabela responsiva é manter a relação entre as células de dados e os cabeçalhos.
- Base semântica: use elementos
<table>padrão com elementos<thead>,<tbody>e<th>. - Contexto fixo: aplique
position: stickyaos cabeçalhos de colunas e linhas. Isso garante que, não importa o quanto um usuário role em qualquer direção, ele nunca perca o contexto do que os dados representam. - Transformações adaptativas: use consultas
@containerem vez de@media. Isso permite que a tabela se adapte com base na própria largura (por exemplo, quando colocada em uma barra lateral ou em um widget de painel estreito), em vez de toda a janela de visualização. - Injeção de rótulo: no layout empilhado, o
<thead>fica oculto, e os cabeçalhos acessíveis são injetados em cada célula usando pseudoelementos::beforee variáveis CSS.
Etapas de implementação
1. Marcação e layout
Estruture sua tabela com cabeçalhos semânticos padrão. Use um .table-wrapper para processar o estouro e fornecer um contêiner para consultas. Adicione um atributo tabindex="0" ao contêiner para garantir que as pessoas que usam apenas um teclado possam rolar a tabela de forma consistente.
Dê ao wrapper um atributo role="region" para comunicar a finalidade dele. Dê um nome acessível usando aria-labelledby (referenciando o atributo id de um elemento que legenda/nomeia a tabela) ou um atributo aria-label.
<div tabindex="0" role="region" aria-labelledby="employee-table-caption" class="table-wrapper">
<table>
<caption id="employee-table-caption">Current employee directory</caption>
<thead>
<tr>
<th>Employee</th>
<th>Role</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<!-- Row header remains sticky horizontally on desktop,
and becomes a sticky card header on mobile -->
<th>Alex Rivera</th>
<td>Engineer</td>
<td>Active</td>
</tr>
</tbody>
</table>
</div>
2. Configurar cabeçalhos fixos
Ative a rolagem e fixe os cabeçalhos. Use propriedades lógicas e valores explícitos de z-index para gerenciar a ordem de empilhamento.
.table-wrapper {
overflow: auto;
max-inline-size: 100%;
max-block-size: min(500px, 80vh);
container-type: inline-size;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* Sticky column headers (desktop) */
thead {
position: sticky;
inset-block-start: 0;
z-index: 1; /* Above sticky row headers */
}
/* Sticky row headers (desktop) */
tbody th {
position: sticky;
inset-inline-start: 0;
background: white; /* Required to cover background during scroll */
}
3. Layout empilhado responsivo
Quando o espaço for limitado, oculte a linha de cabeçalho original e transforme a tabela em cards. Injetar rótulos usando pseudoelementos. Forneça texto alternativo apenas para conteúdo não textual. Caso contrário, o conteúdo do texto e o nome acessível podem ser anunciados pelos leitores de tela.
@container (width < 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead {
/* MANDATORY: Hide column header from layout and screen readers. */
display: none
}
tr {
margin-block-end: 1.5rem;
border: 1px solid #ccc;
}
/* Sticky row headers at the top of each block/card */
tbody th {
position: sticky;
inset-block-start: 0;
z-index: 2;
background: #eee;
}
/* Define column labels as CSS variables (often set via JS) */
table {
--label-1: "Employee";
--label-2: "Role";
--label-3: "Status";
}
/* Inject labels */
td:nth-child(2)::before {
/* MANDATORY: If using non-text content, provide the accessible name (after the /) which ensures screen readers hear the text alternative. */
content: var(--label-2) ": ";
}
/* MANDATORY: Map the label for each column */
td:nth-child(3)::before {
content: var(--label-3) ": ";
}
}