Tabel data adalah susunan dua dimensi yang nilainya saling berkaitan melalui baris dan kolom, dan HTML menampungnya dengan elemen table beserta turunannya. Topik ini membahas struktur dasar tabel, judul dan header yang mendukung aksesibilitas, pengelompokan baris, sel yang melebar, serta batasan kapan tabel sebaiknya tidak digunakan. Setelah membacanya, tabel yang dibangun dapat dibaca dengan benar oleh pembaca layar (screen reader) dan tetap bermakna ketika datanya berubah.
Struktur dasar table, tr, td, dan th#
Elemen table menjadi akar sebuah tabel, elemen tr (table row) membentuk satu baris, dan di dalamnya terdapat sel berupa td (table data) untuk data atau th (table header) untuk kepala sel. Sel th menyatakan bahwa isinya menjadi kepala bagi baris atau kolom lain, sedangkan td menyimpan nilai biasa. Tampilan tebal dan rata tengah pada th hanyalah gaya bawaan browser, sedangkan makna kepala sel itulah yang dimanfaatkan pembaca layar. Setiap sel wajib berada di dalam tr, sehingga kolom tidak dinyatakan secara eksplisit, melainkan lahir dari jumlah sel pada setiap baris. Sel th dan td sama-sama menjadi anak tr, dan perbedaan keduanya hanya pada maknanya.
<table>
<tr>
<th>Nama</th>
<th>Nilai</th>
</tr>
<tr>
<td>Ali</td>
<td>88</td>
</tr>
<tr>
<td>Budi</td>
<td>92</td>
</tr>
</table>Bentuk minimal seperti contoh tersebut sudah valid dan sudah berfungsi, tetapi tabel yang nyata hampir selalu membutuhkan judul dan penandaan arah header. Keduanya dibahas pada bagian berikut, dan keduanya paling berpengaruh ketika tabel dibaca tanpa tampilan visualnya. Selama struktur baris dan selnya terjaga, penambahan kedua unsur itu tidak mengubah apa pun pada data yang sudah ditulis.
caption dan scope untuk aksesibilitas#
Elemen caption memberi judul pada tabel dan ditulis sebagai anak pertama table. Pembaca layar mengumumkan judul ini sebelum membaca sel-selnya, sehingga konteks tabel langsung jelas. Sel th menerima atribut scope yang menyatakan arah keterkaitannya, yaitu scope="col" untuk kepala kolom dan scope="row" untuk kepala baris. Dengan penandaan itu, pembaca layar dapat mengucapkan kepala yang relevan setiap kali sel data dibaca, dan tabel yang memiliki header baris sekaligus header kolom tetap terurai dengan benar.
<table>
<caption>Nilai ujian akhir kelas 8A</caption>
<tr>
<th scope="col">Nama</th>
<th scope="col">Nilai</th>
</tr>
<tr>
<th scope="row">Ali</th>
<td>88</td>
</tr>
<tr>
<th scope="row">Budi</th>
<td>92</td>
</tr>
</table>Sel Ali pada contoh tersebut menjadi kepala bagi nilai 88 di sebelah kanannya, bukan sekadar teks yang kebetulan sebaris. Informasi semacam ini tidak dapat ditebak dari tampilan, karena teks yang ditebalkan secara visual tidak menyatakan keterkaitan apa pun. Itulah alasan header tabel ditulis dengan th dan bukan td yang diberi gaya. Tabel yang memiliki header kolom sekaligus header baris membutuhkan scope pada keduanya, karena tanpa itu urutan pembacaan selnya menjadi samar. Untuk tabel yang sangat rumit, atribut headers pada sel dapat memperjelas keterkaitan, tetapi kebutuhan itu jarang muncul pada tabel biasa.
thead, tbody, dan tfoot#
Baris-baris tabel dapat dikelompokkan menjadi thead untuk baris kepala, tbody untuk baris isi, dan tfoot untuk baris ringkasan. Pengelompokan ini memberi kaitan yang jelas bagi gaya dan skrip, misalnya menahan kepala tabel pada posisinya saat tabel digulir. Perlu diketahui bahwa parser HTML menyisipkan tbody secara implisit apabila tr ditulis langsung di bawah table, sehingga struktur yang tampak di pohon DOM (Document Object Model) bisa berbeda dari markup yang ditulis. Menulis tbody secara eksplisit menghindari kejutan semacam itu.
<table>
<caption>Rincian pengeluaran bulan November</caption>
<thead>
<tr>
<th scope="col">Pos</th>
<th scope="col">Jumlah</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Buku tulis</th>
<td>45.000</td>
</tr>
<tr>
<th scope="row">Pulpen</th>
<td>18.000</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>63.000</td>
</tr>
</tfoot>
</table>Diagram berikut merangkum anatomi tabel lengkap, yaitu caption di puncak, baris kepala pada thead, baris isi pada tbody, dan baris ringkasan pada tfoot.
Baris tbody sering dibangun oleh skrip ketika datanya berubah, misalnya tabel rekap yang diperbarui berkala. Fungsi JavaScript berikut membangun markup baris dari susunan dua dimensi, dan bentuk keluarannya berupa baris td seperti pada contoh tabel nilai ujian dua kolom di atas.
const nilai = [
["Ali", 88],
["Budi", 92],
["Citra", 79],
];
function buatBarisTabel(susunan) {
let markup = "";
for (const [nama, skor] of susunan) {
markup += " <tr>\n";
markup += " <td>" + nama + "</td>\n";
markup += " <td>" + skor + "</td>\n";
markup += " </tr>\n";
}
return markup;
}
console.log(buatBarisTabel(nilai)); <tr>
<td>Ali</td>
<td>88</td>
</tr>
<tr>
<td>Budi</td>
<td>92</td>
</tr>
<tr>
<td>Citra</td>
<td>79</td>
</tr>Susunan dua dimensi dipetakan baris demi baris, dan setiap entri luar menjadi satu tr yang berisi dua td. Hasil keluaran tersebut ditempel di dalam tbody, sehingga skrip hanya mengurus data, sedangkan judul, header, dan ringkasan tetap ditulis manual karena strukturnya tidak berubah.
Sel yang melebar: colspan dan rowspan#
Atribut colspan menggabungkan beberapa kolom pada satu sel, sedangkan rowspan menggabungkan beberapa baris. Sel yang melebar menuntut baris-baris berikutnya berkurang selnya, sehingga grid tetap berbentuk persegi panjang yang utuh. Jadwal piket berikut menggunakan rowspan untuk mengelompokkan dua tugas di bawah satu hari.
<table>
<caption>Jadwal piket kelas 8A</caption>
<tr>
<th scope="col">Hari</th>
<th scope="col">Tugas</th>
</tr>
<tr>
<th scope="row" rowspan="2">Senin</th>
<td>Menyapu lantai</td>
</tr>
<tr>
<td>Merapikan meja</td>
</tr>
<tr>
<th scope="row" rowspan="2">Selasa</th>
<td>Membuang sampah</td>
</tr>
<tr>
<td>Membersihkan papan tulis</td>
</tr>
</table>Baris pertama Senin memuat satu sel data di samping th-nya yang membentang dua baris, sedangkan baris kedua hanya memuat satu td. Apabila pembagian itu tidak dijaga, tabel tampil bergeser dan pembacaan selnya tidak konsisten. Sel yang membentang tetap dapat menjadi kepala, sehingga scope-nya tetap ditulis pada th yang melebar. Pembaca layar mengikuti sel yang tersisa, tetapi penggabungan yang tidak konsisten tetap menyulitkan pembacaan baris demi baris. Karena itu, periksa ulang jumlah sel setiap baris sebelum tabel dianggap selesai. Atribut colspan bekerja dengan cara yang sama pada arah kolom, misalnya colspan="2" pada satu sel ringkasan yang meliputi dua kolom data. Kombinasi keduanya sah, tetapi tabel yang membutuhkan penggabungan rumit biasanya lebih jelas dipecah menjadi dua tabel yang lebih sederhana.
Kapan tabel bukan alat yang tepat#
Tabel hanya tepat untuk data yang terikat baris dan kolom. Deret yang urutannya atau ketiadaan urutannya bermakna bukan tabel, karena deret memiliki ol dan ul, demikian pula pasangan istilah dan penjelasan yang memiliki dl. Memaksa data seperti itu ke dalam tabel membebani pembaca dengan struktur kolom yang tidak menyatakan hubungan apa pun.
Kesalahan yang lebih serius adalah menggunakan tabel untuk tata letak halaman, yaitu praktik warisan masa sebelum CSS memadai. Pembaca layar akan mengumumkan isi halaman sebagai tabel data yang header dan selnya tidak nyata, dan susunannya tidak menyesuaikan lebar layar kecil. Tata letak modern dikerjakan CSS dengan flexbox atau grid, sedangkan table dikembalikan ke tugasnya menyajikan data. Aturan praktisnya sederhana, yaitu apabila isi tidak memiliki baris dan kolom yang saling berkaitan secara makna, elemen yang lebih tepat hampir selalu ada.
Demo berikut menampilkan tabel pengeluaran yang sama dengan contoh di atas, ditambah tombol penanda kerangka. Jalankan demo berikut, klik tombolnya, lalu amati batas caption, thead, tbody, dan tfoot yang ditimbulkan.
Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Demo kerangka tabel</title>
<style>
body { font-family: system-ui, sans-serif; margin: 16px; color: #1e293b; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #cbd5e1; padding: 6px 10px; text-align: left; }
caption { font-weight: 600; padding: 6px; }
button { margin-bottom: 10px; padding: 6px 12px; cursor: pointer; }
.kerangka caption { outline: 3px dashed #7c3aed; }
.kerangka thead { outline: 3px solid #2563eb; }
.kerangka tbody { outline: 3px solid #16a34a; }
.kerangka tfoot { outline: 3px solid #d97706; }
.kerangka th { outline: 2px dotted #0f172a; }
.legenda span { display: inline-block; margin-right: 12px; font-size: 13px; }
.l1 { border-bottom: 3px solid #2563eb; }
.l2 { border-bottom: 3px solid #16a34a; }
.l3 { border-bottom: 3px solid #d97706; }
.l4 { border-bottom: 3px dashed #7c3aed; }
</style>
</head>
<body>
<button type="button" id="tombol-kerangka">Tampilkan kerangka</button>
<table id="tabel">
<caption>Rincian pengeluaran November</caption>
<thead>
<tr>
<th scope="col">Pos</th>
<th scope="col">Jumlah</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Buku tulis</th>
<td>45.000</td>
</tr>
<tr>
<th scope="row">Pulpen</th>
<td>18.000</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>63.000</td>
</tr>
</tfoot>
</table>
<p class="legenda">
<span class="l4">caption</span>
<span class="l1">thead</span>
<span class="l2">tbody</span>
<span class="l3">tfoot</span>
</p>
<script>
const tabel = document.getElementById('tabel');
const tombol = document.getElementById('tombol-kerangka');
tombol.addEventListener('click', () => {
tabel.classList.toggle('kerangka');
if (tabel.classList.contains('kerangka')) {
tombol.textContent = 'Sembunyikan kerangka';
} else {
tombol.textContent = 'Tampilkan kerangka';
}
});
</script>
</body>
</html>Menyatukan contoh dalam satu berkas#
Berkas berikut memuat dua tabel yang melengkapi satu sama lain. Tabel pertama adalah rekap nilai dengan caption, thead, tbody, dan tfoot yang lengkap, sedangkan tabel kedua memperlihatkan rowspan dan colspan pada jadwal ujian. Seluruh th di dalamnya menggunakan scope, dan kepala barisnya ditandai scope="row" sehingga setiap nilai terkait dengan pemiliknya. Struktur tabel kedua sekaligus menunjukkan bahwa penggabungan sel tidak mengubah cara th dan td ditulis. Kedua tabelnya juga diberi caption masing-masing karena konteksnya berbeda.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Contoh tabel data</title>
</head>
<body>
<h1>Rekap nilai ujian akhir</h1>
<table>
<caption>Nilai ujian akhir kelas 8A</caption>
<thead>
<tr>
<th scope="col">Nama</th>
<th scope="col">Ulangan harian</th>
<th scope="col">Ujian akhir</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Ali</th>
<td>85</td>
<td>88</td>
</tr>
<tr>
<th scope="row">Budi</th>
<td>90</td>
<td>92</td>
</tr>
<tr>
<th scope="row">Citra</th>
<td>82</td>
<td>79</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Rata-rata kelas</th>
<td>85,7</td>
<td>86,3</td>
</tr>
</tfoot>
</table>
<h2>Rincian hari ujian</h2>
<table>
<caption>Jadwal ujian semester</caption>
<thead>
<tr>
<th scope="col">Hari</th>
<th scope="col">Mata pelajaran</th>
<th scope="col">Durasi</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" rowspan="2">Senin</th>
<td>Matematika</td>
<td>120 menit</td>
</tr>
<tr>
<td>Bahasa Indonesia</td>
<td>90 menit</td>
</tr>
<tr>
<th scope="row">Selasa</th>
<td colspan="2">Pilihan proyek (tanpa ujian tertulis)</td>
</tr>
</tbody>
</table>
</body>
</html>Buka berkasnya di browser, lalu perhatikan bahwa rata-rata pada tfoot dan penggabungan sel pada tabel kedua tetap terbaca sebagai baris biasa tanpa trik apa pun. Salin berkasnya, tambahkan satu baris siswa pada tbody, lalu hitung ulang rata-ratanya secara manual untuk merasakan bagian mana yang berubah. Percobaan berguna berikutnya adalah menghapus satu baris dan melihat bahwa kerangka thead dan tfoot tidak ikut rusak, karena kerangka itu dideklarasikan sekali di luar data. Latihan kecil semacam ini menegaskan bahwa data dan kerangka tabel memang terpisah.
Kesalahan umum#
Tiga kesalahan berikut sering lolos karena tampilannya tampak benar. Ketiganya berkaitan dengan perbedaan antara tampilan visual dan struktur yang dibaca mesin, dan perbaikannya cukup menyunting kembali markupnya, bukan menambah gaya.
- Mengganti
thdengantdyang ditebalkan lewat CSS. Tampilannya serupa, tetapi pembaca layar kehilangan peta header, dan nilai sel terbaca tanpa kepemilikan yang jelas. Tulis header denganth, lalu beriscopebila arahnya perlu ditegaskan. - Menulis tabel tanpa
caption. Konteksnya cepat lupa setelah tabel dibaca sendirian, terutama oleh pembaca layar yang mengumumkan tabel lalu isinya. Tambahkancaptionyang menyebut isi dan periodenya, misalnya bulan dan tahun pengeluarannya. - Menulis
colspanataurowspantanpa mengurangi sel baris-baris lainnya. Grid menjadi bolong, dan sel-sel setelahnya bergeser ke kolom yang salah. Kesalahan ini paling mudah ditemukan dengan menambahkan border sementara pada seluruh sel melalui CSS, lalu menghitung ulang jumlah sel setiap baris sehingga totalnya tetap sama dengan jumlah kolom.
Ringkasan#
table,tr,td, danthmembentuk kerangka tabel, danthmenyatakan kepala sel denganscope="col"atauscope="row".captionmemberi judul tabel, sedangkanthead,tbody, dantfootmengelompokkan baris kepala, isi, dan ringkasan.- Parser menyisipkan
tbodysecara implisit bilatrditulis langsung di bawahtable. colspandanrowspanmenggabungkan sel dengan syarat grid tetap utuh, dan tabel bukan alat tata letak halaman.
Referensi#
- Dokumentasi elemen table — MDN — Struktur table beserta seluruh elemen pembentuknya.
- Dokumentasi elemen caption — MDN — Posisi caption dan manfaatnya bagi aksesibilitas.
- HTML table basics — MDN — Panduan menyusun tabel lengkap dengan header dan penggabungan sel.
- Tabular data — HTML Living Standard (WHATWG) — Spesifikasi resmi tabel beserta atribut scope, colspan, dan rowspan.
- Learn HTML: Tables — web.dev — Ringkasan tabel dengan penekanan pada aksesibilitas.