Tabel adalah data yang disusun dalam baris dan kolom, dan tiap sel ada di persilangan satu judul baris dan satu judul kolom. Bayangin buku tamu di meja Bu Sri yang sudah digaris: kolom Tamu, Kamar, Check-in, Malam, Total. Untuk tahu berapa yang dibayar Joko, matamu turun ke baris Joko lalu menyeberang ke kolom Total. Tabel di HTML bekerja sama persis, dan screen reader juga membacanya dengan cara itu, asal kamu memberi tahu mana yang judul.
🧪 Tebakan cepat: Tabel 16 pesanan Bu Sri selebar 446px. Bu Sri membukanya di HP selebar 375px, tanpa CSS tambahan apa pun. Apa yang terjadi: kolom paling kanan terpotong, teks turun ke baris baru, atau seluruh halaman bisa digeser ke samping? Jawabannya ada di bagian "Tabel di layar 375px".
Tabel untuk data yang dibandingkan
Di pelajaran density, dasbor Bu Sri menampilkan 16 pesanan sebagai daftar. Sekarang Bu Sri ingin membandingkan: siapa menginap paling lama, pesanan mana yang paling besar. Begitu tiap baris punya isian yang sama (tamu, kamar, tanggal, malam, total) dan orang membandingkannya antar baris, pakai tabel. Kalau tiap item berdiri sendiri, seperti tiga kartu kamar untuk tamu dengan foto dan tombol pesan, pakai kartu atau daftar.
Tabel pesanan Bu Sri
<table>
<caption id="cap">Pesanan Desember (16)</caption>
<thead>
<tr>
<th scope="col">Tamu</th>
<th scope="col">Kamar</th>
<th scope="col">Check-in</th>
<th scope="col" class="num">Malam</th>
<th scope="col" class="num">Total</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">Rina</th><td>Teras</td><td>1 Des</td><td class="num">2</td><td class="num">Rp 700.000</td></tr>
<tr><th scope="row">Pak Hadi</th><td>Kebun</td><td>2 Des</td><td class="num">1</td><td class="num">Rp 425.000</td></tr>
<!-- 14 pesanan lagi -->
</tbody>
</table>
<style>
table { border-collapse: collapse; width: 100%; }
caption { text-align: left; font-weight: 700; padding-bottom: 8px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #e2e8f0; white-space: nowrap; }
thead th { border-bottom: 2px solid #e2e8f0; color: #475569; font-size: 14px; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
</style>
<caption>adalah nama tabelnya. Screen reader menyebutnya saat masuk ke tabel: "Pesanan Desember (16), tabel, 5 kolom".<thead>membungkus baris judul,<tbody>membungkus baris data. Dua-duanya ada di dalam<table>.<th scope="col">artinya "aku judul untuk sel-sel di bawahku".<th scope="row">di nama tamu artinya "aku judul untuk sel-sel di sampingku". Saat screen reader berhenti di "Rp 700.000", dia ikut menyebut "Rina" dan "Total", jadi orang tahu angka itu milik siapa dan apa.border-collapse: collapsemenyatukan garis sel yang bersebelahan jadi satu garis. Tanpa itu, tiap sel punya garisnya sendiri dengan celah 2px di antaranya (border-spacingbawaan browser).white-space: nowrapmenjaga "Rp 1.275.000" dan "Pak Hadi" tetap satu baris.
Angka rata kanan, digitnya selebar sama
Teks dibaca dari kiri, tapi angka dibandingkan dari kanan: satuan di bawah satuan, ribuan di bawah ribuan. Kalau kolom Total rata kiri, "Rp 275.000" dan "Rp 1.275.000" mulai di titik yang sama, dan mata harus menghitung digit untuk tahu mana yang jutaan. Di dasbor Bu Sri, tepi kanan kolom Total yang rata kiri meleset sampai 10,8px. Rata kanan membuat digit terakhirnya sejajar, jadi angka yang lebih panjang langsung terlihat lebih besar. Jadi angka di tabel diratakan ke kanan; teks tetap rata kiri.
Satu lagi: di banyak font, digit gak sama lebar. Kami mengukur font sistem di Mac (San Francisco, 16px) di Chrome, Safari, dan Firefox: "1111111" selebar 49,77px, "0000000" selebar 68,36px. Jadi "1" cuma 7,11px dan "0" 9,77px. Dengan font-variant-numeric: tabular-nums, semua digit jadi 9,77px. Hasilnya di kolom Total: rata kanan saja sudah menyejajarkan digit terakhir, tapi tepi kiri "Rp 700.000", "Rp 425.000", dan angka enam digit lainnya masih meleset sampai 1,6px, karena digitnya gak sama lebar. Dengan tabular-nums, tiap kolom digit sejajar sampai pikselnya.
Mengurutkan dengan aria-sort
Bu Sri ingin tahu pesanan terbesar. Judul kolom diberi tombol, dan kolom yang sedang dipakai mengurutkan diberi aria-sort:
<th scope="col" class="num" aria-sort="descending">
<button type="button">Total</button>
</th>
const tbody = document.querySelector('tbody');
const total = (row) => Number(row.cells[4].textContent.replace(/\D/g, ''));
const rows = [...tbody.rows].sort((a, b) => total(b) - total(a)); // terbesar dulu
tbody.append(...rows); // pindahkan baris sesuai urutan baru
aria-sortditulis di<th>, bukan di tombolnya, dan hanya di satu kolom yang sedang mengurutkan. Nilainyaascending(kecil ke besar) ataudescending(besar ke kecil). Kolom lain gak diberiaria-sortsama sekali.<button>membuat judulnya bisa dipilih dengan Tab dan ditekan dengan Enter.row.cells[4]adalah sel kelima, Total.replace(/\D/g, '')membuang semua yang bukan digit, jadi "Rp 1.275.000" jadi 1275000.- Hasilnya: Joko dan Eko (Rp 1.275.000) paling atas, lalu Maya dan Wulan (Rp 850.000). Joko di atas Eko karena
sortdi JavaScript stabil: yang nilainya sama tetap di urutan semula.
Tabel di layar 375px
Kami membuka tabel ini di HP 375px di Chrome, Safari, dan Firefox. Tabelnya gak menyusut di bawah 446px, karena tiap sel nowrap. Halaman jadi selebar 462px (446 + padding 16px), jadi seluruh halaman bisa digeser ke samping 87px, judul dan semua isinya ikut bergeser. Gak ada yang terpotong, dan gak ada yang turun baris. Itu jawaban tebakannya.
Ada dua pola yang memperbaikinya:
<div class="scroll" role="region" aria-labelledby="cap" tabindex="0">
<table> ... </table>
</div>
<style>
.scroll { overflow-x: auto; } /* pola 1: kotak scroll */
@media (max-width: 480px) { .opt { display: none; } } /* pola 2: kolom ringkas */
</style>
- Kotak scroll: halamannya kembali 375px, cuma tabelnya yang digeser di dalam kotaknya sendiri.
tabindex="0"membuat kotak itu bisa dipilih dengan Tab lalu digeser dengan tombol panah, danrole="region"denganaria-labelledby="cap"memberinya nama dari caption. WCAG 1.4.10 (Reflow) memang mengecualikan tabel data dari larangan scroll ke samping, karena barisnya harus dibaca utuh. - Kolom ringkas: kolom Kamar dan Malam diberi
class="opt"dan disembunyikan di bawah 480px. Tabelnya pas 343px, tapi Bu Sri kehilangan dua informasi. Pakai hanya kalau kolom itu memang jarang dibutuhkan di HP.
⚠️ Jebakan yang sering ketemu
<th>tanpascope, atau<td>yang ditebalkan sebagai judul. Terlihat seperti judul, tapi screen reader gak tahu sel mana yang dijudulinya.- Angka rata kiri. Ribuan dan jutaan gak sejajar, dan mata harus menghitung digit. Rata kanan, plus
tabular-nums. aria-sortdi semua kolom, atau di<button>. Cukup satu, di<th>kolom yang sedang mengurutkan.overflow: hiddendi pembungkus tabel. Kolom kanan terpotong dan gak bisa dicapai sama sekali. Pakaioverflow-x: auto.- Tabel untuk tata letak. Kalau gak ada judul baris dan kolom yang masuk akal, itu bukan tabel. Pakai grid atau flex.
TL;DR: Pakai tabel untuk data yang dibandingkan antar baris. <caption> memberi nama, <thead> dan <tbody> memisahkan judul dan data, <th scope="col"> menjuduli kolom dan <th scope="row"> menjuduli baris, supaya screen reader menyebut "Rina, Total, Rp 700.000". table { border-collapse: collapse; } menyatukan garis sel. Angka diratakan ke kanan dengan font-variant-numeric: tabular-nums (di font Mac "1" 7,11px dan "0" 9,77px tanpa itu). Kolom yang mengurutkan diberi aria-sort="ascending" atau "descending" di <th>-nya, dengan <button> di dalamnya. Di HP 375px, tabel 446px membuat seluruh halaman bergeser 87px: bungkus dengan <div class="scroll" role="region" aria-labelledby="cap" tabindex="0"> dan overflow-x: auto, atau sembunyikan kolom yang jarang dipakai.