Tabel responsif

Tabel data besar sering kali menjadi tidak terbaca di layar kecil karena kolom meluap atau menyusut hingga tidak terbaca. Panduan ini menunjukkan cara menggunakan pemosisian melekat agar header tetap terlihat saat men-scroll dan cara mengubah tabel menjadi tata letak "bertumpuk" yang mobile-friendly saat ruang terbatas.

Inti dari tabel responsif adalah mempertahankan hubungan antara sel data dan headernya.

  1. Fondasi Semantik: Gunakan elemen <table> standar dengan elemen <thead>, <tbody>, dan <th>.
  2. Konteks Tetap: Terapkan position: sticky ke header kolom dan header baris. Hal ini memastikan bahwa seberapa jauh pun pengguna men-scroll ke arah mana pun, mereka tidak akan pernah kehilangan konteks dari apa yang diwakili oleh data tersebut.
  3. Transformasi Adaptif: Gunakan kueri @container, bukan kueri @media. Hal ini memungkinkan tabel beradaptasi berdasarkan lebarnya sendiri (misalnya, saat ditempatkan di sidebar atau widget dasbor yang sempit), bukan seluruh area pandang.
  4. Injeksi Label: Dalam tata letak bertumpuk, <thead> disembunyikan, dan header yang dapat diakses dimasukkan ke dalam setiap sel menggunakan elemen semu ::before dan variabel CSS.

Langkah-Langkah Penerapan

1. Markup dan Tata Letak

Susun tabel Anda dengan header semantik standar. Gunakan .table-wrapper untuk menangani overflow dan menyediakan penampung untuk kueri. Tambahkan atribut tabindex="0" ke penampung, untuk memastikan bahwa orang yang hanya menggunakan keyboard dapat men-scroll tabel secara konsisten.

Beri wrapper atribut role="region" untuk menyampaikan tujuannya. Berikan label aksesibilitas menggunakan aria-labelledby (merujuk ke atribut id dari elemen yang ada yang memberi teks/nama pada tabel) atau atribut 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. Mengonfigurasi Header Melekat

Aktifkan scroll dan buat header tetap. Gunakan properti logis dan nilai z-index eksplisit untuk mengelola urutan tumpukan.

.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. Tata Letak Bertumpuk Responsif

Jika ruang terbatas, sembunyikan baris header asli dan ubah tabel menjadi kartu. Memasukkan label menggunakan elemen semu. Hanya berikan teks alternatif untuk konten non-teks. Jika tidak, konten teks dan label aksesibilitas dapat diucapkan oleh pembaca layar.

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