Le tabelle di dati di grandi dimensioni spesso diventano illeggibili su schermi piccoli, poiché le colonne si espandono o si riducono oltre la leggibilità. Questa guida mostra come utilizzare il posizionamento fisso per mantenere visibili le intestazioni durante lo scorrimento e come trasformare la tabella in un layout "impilato" ottimizzato per il mobile quando lo spazio è limitato.
Approccio consigliato
L'aspetto fondamentale di una tabella adattabile è mantenere la relazione tra le celle di dati e le relative intestazioni.
- Base semantica: utilizza elementi
<table>standard con elementi<thead>,<tbody>e<th>. - Contesto fisso: applica
position: stickysia alle intestazioni di colonna che a quelle di riga. In questo modo, indipendentemente da quanto un utente scorre in qualsiasi direzione, non perde mai il contesto di ciò che rappresentano i dati. - Trasformazioni adattive: utilizza le query
@containeranziché le query@media. In questo modo, la tabella può adattarsi in base alla propria larghezza (ad esempio, se inserita in una barra laterale o in un widget di una dashboard stretta) anziché all'intera area visibile. - Inserimento di etichette: nel layout impilato, l'elemento
<thead>è nascosto e le intestazioni accessibili vengono inserite in ogni cella utilizzando pseudo-elementi::beforee variabili CSS.
Procedura di implementazione
1. Markup e layout
Struttura la tabella con intestazioni semantiche standard. Utilizza un .table-wrapper per gestire l'overflow e fornire un container per le query. Aggiungi un attributo tabindex="0" al contenitore per assicurarti che le persone che utilizzano solo una tastiera possano scorrere la tabella in modo coerente.
Assegna al wrapper un attributo role="region" per comunicarne lo scopo. Assegna un nome accessibile utilizzando aria-labelledby (che fa riferimento all'attributo id di un elemento esistente che fornisce una didascalia/un nome alla tabella) o un attributo 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. Configura le intestazioni fisse
Attiva lo scorrimento e rendi le intestazioni fisse. Utilizza le proprietà logiche e i valori z-index espliciti per gestire l'ordine di sovrapposizione.
.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 impilato adattabile
Quando lo spazio è limitato, nascondi la riga di intestazione originale e trasforma la tabella in schede. Inserisci le etichette utilizzando gli pseudo-elementi. Fornisci un testo alternativo solo per i contenuti non testuali, altrimenti gli screen reader potrebbero annunciare sia i contenuti testuali sia il nome accessibile.
@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) ": ";
}
}