Tabele elastyczne

Duże tabele danych często stają się nieczytelne na małych ekranach, ponieważ kolumny przepełniają się lub kurczą do rozmiarów uniemożliwiających odczytanie. Z tego przewodnika dowiesz się, jak używać pozycjonowania przyklejonego, aby nagłówki były widoczne podczas przewijania, oraz jak przekształcić tabelę w układ „skumulowany” dostosowany do komórek, gdy ilość miejsca jest ograniczona.

Podstawą tabeli elastycznej jest zachowanie relacji między komórkami danych a ich nagłówkami.

  1. Podstawy semantyczne: używaj standardowych elementów <table> z elementami <thead>, <tbody> i <th>.
  2. Przyklejony kontekst: zastosuj position: sticky zarówno do nagłówków kolumn, jak i nagłówków wierszy. Dzięki temu niezależnie od tego, jak daleko użytkownik przewinie ekran w dowolnym kierunku, zawsze będzie wiedział, co oznaczają dane.
  3. Przekształcenia adaptacyjne: używaj zapytań @container zamiast @media. Dzięki temu tabela może dostosowywać się do własnej szerokości (np. gdy jest umieszczona na pasku bocznym lub w wąskim widżecie panelu), a nie do całego widocznego obszaru.
  4. Wstrzykiwanie etykiet: w układzie skumulowanym element <thead> jest ukryty, a dostępne nagłówki są wstrzykiwane do każdej komórki za pomocą pseudoelementów ::before i zmiennych CSS.

Etapy wdrażania

1. Znaczniki i układ

Uporządkuj tabelę za pomocą standardowych nagłówków semantycznych. Użyj .table-wrapper, aby obsłużyć przepełnienie i zapewnić kontener na zapytania. Dodaj do kontenera atrybut tabindex="0", aby użytkownicy korzystający tylko z klawiatury mogli spójnie przewijać tabelę.

Dodaj do kontenera atrybut role="region", aby poinformować o jego przeznaczeniu. Nadaj mu nazwę na potrzeby ułatwień dostępu, używając atrybutu aria-labelledby (odwołującego się do atrybutu id istniejącego elementu, który zawiera podpis lub nazwę tabeli) lub atrybutu 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. Konfigurowanie przyklejonych nagłówków

Włącz przewijanie i ustaw nagłówki jako przyklejone. Do zarządzania kolejnością nakładania elementów używaj właściwości logicznych i jawnych wartości kolejności nakładania elementów.

.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. Elastyczny układ kaskadowy

Gdy ilość miejsca jest ograniczona, ukryj oryginalny wiersz nagłówka i przekształć tabelę w karty. Wstrzykiwanie etykiet za pomocą pseudoelementów. Tekst alternatywny podawaj tylko w przypadku treści niebędących tekstem. W przeciwnym razie czytniki ekranu mogą odczytywać zarówno treść tekstową, jak i nazwę na potrzeby ułatwień dostępu.

@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) ": ";
  }
}