Sesudah kartu ringkasan merangkum gambaran besar, tabel transaksi menyajikan rinciannya: kapan uang berpindah, untuk apa, dan berapa. Tabel adalah komponen yang paling sering dirusak oleh tata letak yang dipaksakan, jadi kita menaatinya dengan cara yang benar — elemen table tetap semantik, pembungkusnya yang mengatur gulir, dan CSS hanya menata keterbacaan. Bagian ini juga memperkenalkan pola yang jarang diajarkan: kepala kolom yang tetap terlihat saat tabel digulir ke bawah.
Tabel Semantik di dalam Area Gulir #
Struktur HTML-nya berlapis tiga: section.seksi-transaksi sebagai kartu pembungkus, div.tabel-gulir sebagai area gulir, lalu table.tabel-transaksi yang murni data. Pembagian peran ini penting:
.tabel-gulir { max-height: 380px; overflow-y: auto; }
.tabel-transaksi { width: 100%; border-collapse: collapse; font-size: 14px; }Deklarasi border-collapse: collapse menyatukan garis antarsel sehingga cukup satu border-bottom per baris, bukan border ganda yang bertumpuk. Pembatas max-height: 380px menjaga tabel delapan baris ini tetap ringkas; bila datanya bertambah menjadi lima puluh baris, gulirnya tumbuh di dalam pembungkus tanpa memaksa dasbor memanjang. Mesin pencari dan pembaca layar tetap membaca struktur thead, tbody, caption, serta scope="col" pada tiap kolom.
Kepala Kolom yang Menempel #
Saat pengguna menggulir daftar transaksi yang panjang, kepala kolom yang ikut bergulir membuat mereka harus mengingat-ingat arti tiap angka. Properti position: sticky menyelesaikannya dengan satu aturan:
.tabel-transaksi thead th {
position: sticky; top: 0; z-index: 1;
background-color: var(--warna-utama-lembut); color: var(--warna-utama-tinta);
}Baris thead menempel di tepi atas .tabel-gulir — kontainer gulir terdekatnya — dan tetap terlihat selama gulir berlangsung. Dua syarat teknisnya terpenuhi oleh aturan yang sama: top: 0 menentukan titik menempel, dan latar belakang tidak transparan menutupi isi baris yang lewat di bawahnya; tanpa latar pekat, isi tabel akan terlihat menembus kepala kolom. Nilai z-index: 1 menjaga kepala kolom berada di atas sel biasa.
Angka Rata Kanan dan Lencana Status #
Konvensi laporan keuangan menuntut kolom jumlah rata kanan agar digit satuan, ribuan, dan jutaan membentuk garis lurus:
.kolom-jumlah {
text-align: right; font-weight: 700;
font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sel-masuk { color: var(--warna-masuk); }
.sel-keluar { color: var(--warna-keluar); }Warna hijau dan merah pada angka memakai token yang sama dengan kartu ringkasan, dan tanda plus atau minus di depan nilai membuat informasinya tetap utuh bagi yang tidak melihat warna. Status tiap baris ditampilkan sebagai lencana kecil dengan pasangan latar dan teks yang kontrasnya aman dibaca:
.lencana-status { padding: 3px 12px; border-radius: var(--radius-penuh); font-size: 12px; font-weight: 700; }
.lencana-status--masuk { background-color: var(--warna-masuk-latar); color: var(--warna-masuk); }
.lencana-status--keluar { background-color: var(--warna-keluar-latar); color: var(--warna-keluar); }Pasangan #15803d di atas #dcfce7 dan #b91c1c di atas #fee2e2 sama-sama melampaui ambang kontras teks kecil standar WCAG AA. Pola “teks pekat di atas latar pucat satu keluarga warna” ini jauh lebih aman daripada membaliknya menjadi teks putih di atas warna jenuh, yang mudah gagal kontras pada ukuran 12 px.
Gulir tabel pada demo ke bawah dan ke atas: baris kepala kolom tetap menempel di atasnya dengan latar mint, sementara garis zebra dan angka rata kanan mengikuti barisnya masing-masing.
/* ============================================================
Dashboard Keuangan — stylesheet utama
Urutan: token → base → cangkang aplikasi → sidebar →
kepala aplikasi → kartu ringkasan → tabel transaksi →
lencana status → responsif → fokus dan gerak
============================================================ */
/* ---------- 1. Design token ---------- */
:root {
/* Warna semantik dasbor */
--warna-latar: #f1f5f9; --warna-permukaan: #ffffff;
--warna-teks: #0f172a; --warna-teks-lembut: #64748b;
--warna-garis: #e2e8f0; --warna-utama: #0f766e;
--warna-utama-pekat: #115e59; --warna-utama-lembut: #ccfbf1;
--warna-utama-tinta: #134e4a; --warna-fokus: #0d9488;
--warna-samping: #0f172a; --warna-samping-teks: #cbd5e1;
--warna-samping-hover: rgba(255, 255, 255, 0.08);
--warna-masuk: #15803d; --warna-masuk-latar: #dcfce7;
--warna-keluar: #b91c1c; --warna-keluar-latar: #fee2e2;
/* Skala spasi, basis empat dan delapan piksel */
--jarak-1: 4px; --jarak-2: 8px;
--jarak-3: 12px; --jarak-4: 16px;
--jarak-5: 24px; --jarak-6: 32px;
/* Radius sudut dan bayangan */
--radius-kecil: 8px; --radius: 12px; --radius-penuh: 999px;
--bayangan-1: 0 1px 2px rgba(15, 23, 42, 0.06);
--bayangan-2: 0 12px 28px rgba(15, 23, 42, 0.22);
/* Tipografi, gerak, dan kerangka */
--font-dasar: system-ui, "Segoe UI", sans-serif;
--durasi: 180ms; --pelunakan: ease-out;
--lebar-samping: 248px;
}
/* ---------- 2. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-family: var(--font-dasar); line-height: 1.6; background-color: var(--warna-latar); color: var(--warna-teks); }
h1, h2, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
button { font: inherit; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* ---------- 3. Cangkang aplikasi ---------- */
.cangkang-app {
display: grid;
grid-template-areas: "samping kepala" "samping utama";
grid-template-columns: var(--lebar-samping) 1fr;
grid-template-rows: auto 1fr;
height: 100dvh;
}
.sidebar { grid-area: samping; }
.kepala-app { grid-area: kepala; }
.isi-utama { grid-area: utama; overflow-y: auto; display: grid; gap: var(--jarak-5); align-content: start; padding: var(--jarak-5); }
.isi-kaki { font-size: 12.5px; color: var(--warna-teks-lembut); text-align: center; }
/* ---------- 4. Sidebar dan navigasi ---------- */
.sidebar {
display: flex; flex-direction: column; gap: var(--jarak-5); overflow-y: auto;
padding: var(--jarak-5) var(--jarak-4);
background-color: var(--warna-samping); color: var(--warna-samping-teks);
}
.brand { display: flex; align-items: center; gap: var(--jarak-3); }
.brand-tanda { flex: none; width: 38px; height: 38px; border-radius: 10px; background-image: linear-gradient(135deg, #2dd4bf, #0f766e); }
.brand-nama { color: #ffffff; font-size: 17px; font-weight: 800; line-height: 1.3; }
.brand-sub { display: block; font-size: 11.5px; font-weight: 500; color: var(--warna-samping-teks); }
.menu-judul { padding-inline: var(--jarak-3); font-size: 11.5px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: #64748b; }
.menu-app { display: grid; gap: 4px; margin-top: var(--jarak-2); }
.menu-tautan {
display: flex; align-items: center; gap: var(--jarak-3); padding: 10px var(--jarak-3);
border-radius: var(--radius-kecil); text-decoration: none;
color: var(--warna-samping-teks); font-size: 14.5px; font-weight: 600;
transition: background-color var(--durasi) var(--pelunakan), color var(--durasi) var(--pelunakan);
}
.menu-tautan:hover { background-color: var(--warna-samping-hover); color: #ffffff; }
.menu-tautan.is-aktif { background-color: var(--warna-utama); color: #ffffff; }
.sidebar-kaki { margin-top: auto; padding: var(--jarak-3); border-top: 1px solid rgba(255, 255, 255, 0.12); font-size: 12.5px; }
/* ---------- 5. Kepala aplikasi ---------- */
.kepala-app {
display: flex; align-items: center; gap: var(--jarak-4); padding: var(--jarak-4) var(--jarak-5);
background-color: var(--warna-permukaan); border-bottom: 1px solid var(--warna-garis);
}
.kepala-sapaan { font-size: 12.5px; color: var(--warna-teks-lembut); }
.kepala-judul { font-size: 20px; font-weight: 800; letter-spacing: -0.01em; line-height: 1.3; }
.kepala-periode { margin-left: auto; padding: 6px 14px; border-radius: var(--radius-penuh); background-color: var(--warna-utama-lembut); color: var(--warna-utama-tinta); font-size: 13px; font-weight: 700; }
.tombol-samping { display: none; }
/* ---------- 6. Kartu ringkasan ---------- */
.dasbor-ringkasan {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: var(--jarak-4);
}
.kartu-ringkasan {
display: grid; gap: 2px; padding: var(--jarak-4) var(--jarak-5);
background-color: var(--warna-permukaan); border-radius: var(--radius);
border: 1px solid var(--warna-garis); border-left: 4px solid var(--warna-utama);
box-shadow: var(--bayangan-1);
}
.kartu-ringkasan--masuk { border-left-color: var(--warna-masuk); }
.kartu-ringkasan--keluar { border-left-color: var(--warna-keluar); }
.kartu-label { font-size: 13px; font-weight: 600; color: var(--warna-teks-lembut); }
.kartu-angka { font-size: clamp(1.5rem, 2.5vw, 1.85rem); font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.kartu-tren { font-size: 13px; font-weight: 600; color: var(--warna-teks-lembut); }
.tren--naik::before { content: "▲ "; font-size: 0.8em; }
.tren--turun::before { content: "▼ "; font-size: 0.8em; }
.tren--positif { color: var(--warna-masuk); }
.tren--negatif { color: var(--warna-keluar); }
/* ---------- 7. Tabel transaksi ---------- */
.seksi-transaksi {
background-color: var(--warna-permukaan); border-radius: var(--radius); overflow: hidden;
border: 1px solid var(--warna-garis); box-shadow: var(--bayangan-1);
}
.seksi-kepala {
display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
gap: var(--jarak-3); padding: var(--jarak-4) var(--jarak-5);
border-bottom: 1px solid var(--warna-garis);
}
.seksi-judul { font-size: 17px; font-weight: 800; }
.tautan-lain { font-size: 13.5px; font-weight: 600; color: var(--warna-utama); text-decoration: none; }
.tautan-lain:hover { text-decoration: underline; }
.tabel-gulir { max-height: 380px; overflow-y: auto; }
.tabel-transaksi { width: 100%; border-collapse: collapse; font-size: 14px; }
.tabel-transaksi th, .tabel-transaksi td {
padding: 12px var(--jarak-5); text-align: left;
border-bottom: 1px solid var(--warna-garis);
}
.tabel-transaksi thead th {
position: sticky; top: 0; z-index: 1;
background-color: var(--warna-utama-lembut); color: var(--warna-utama-tinta);
font-size: 12.5px; letter-spacing: 0.05em; text-transform: uppercase;
}
.tabel-transaksi tbody tr:nth-child(even) { background-color: #f8fafc; }
.tabel-transaksi tbody tr:last-child td { border-bottom: none; }
.kolom-tanggal { white-space: nowrap; color: var(--warna-teks-lembut); }
.kolom-kategori { color: var(--warna-teks-lembut); }
.kolom-jumlah { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.sel-masuk { color: var(--warna-masuk); }
.sel-keluar { color: var(--warna-keluar); }
/* ---------- 8. Lencana status ---------- */
.lencana-status { display: inline-block; padding: 3px 12px; border-radius: var(--radius-penuh); font-size: 12px; font-weight: 700; }
.lencana-status--masuk { background-color: var(--warna-masuk-latar); color: var(--warna-masuk); }
.lencana-status--keluar { background-color: var(--warna-keluar-latar); color: var(--warna-keluar); }<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Keuangan — DompetKu</title>
<link rel="stylesheet" href="css/styles.css">
<script src="js/main.js" defer></script>
</head>
<body>
<div class="cangkang-app">
<aside class="sidebar" id="sidebar-nav" aria-label="Panel navigasi dasbor">
<div class="brand">
<span class="brand-tanda" aria-hidden="true"></span>
<span class="brand-nama">DompetKu<span class="brand-sub">Catatan keuangan pribadi</span></span>
</div>
<nav aria-label="Menu utama">
<p class="menu-judul" id="judul-menu">Menu</p>
<ul class="menu-app" aria-labelledby="judul-menu">
<li><a href="#ringkasan" class="menu-tautan is-aktif" aria-current="page">Ringkasan</a></li>
<li><a href="#transaksi" class="menu-tautan">Transaksi</a></li>
<li><a href="#" class="menu-tautan">Anggaran</a></li>
<li><a href="#" class="menu-tautan">Tabungan</a></li>
<li><a href="#" class="menu-tautan">Laporan</a></li>
</ul>
<p class="menu-judul" id="judul-menu-lain">Lainnya</p>
<ul class="menu-app" aria-labelledby="judul-menu-lain">
<li><a href="#" class="menu-tautan">Pengaturan</a></li>
<li><a href="#" class="menu-tautan">Bantuan</a></li>
</ul>
</nav>
<p class="sidebar-kaki">Versi latihan 1.0</p>
</aside>
<button type="button" class="tirai-samping" id="tirai-samping" aria-label="Tutup panel navigasi"></button>
<header class="kepala-app">
<button type="button" class="tombol-samping" id="tombol-samping" aria-expanded="false" aria-controls="sidebar-nav">☰ Menu</button>
<div class="kepala-teks">
<p class="kepala-sapaan">Selamat datang kembali, Rani</p>
<h1 class="kepala-judul">Ringkasan Keuangan</h1>
</div>
<p class="kepala-periode">November 2026</p>
</header>
<main class="isi-utama" id="ringkasan">
<section class="dasbor-ringkasan" aria-label="Kartu ringkasan keuangan">
<article class="kartu-ringkasan">
<p class="kartu-label">Saldo saat ini</p>
<p class="kartu-angka">Rp9.485.000</p>
<p class="kartu-tren tren tren--turun tren--negatif">0,8% dari bulan lalu</p>
</article>
<article class="kartu-ringkasan kartu-ringkasan--masuk">
<p class="kartu-label">Pemasukan bulan ini</p>
<p class="kartu-angka">Rp11.670.000</p>
<p class="kartu-tren tren tren--naik tren--positif">12,4% dari bulan lalu</p>
</article>
<article class="kartu-ringkasan kartu-ringkasan--keluar">
<p class="kartu-label">Pengeluaran bulan ini</p>
<p class="kartu-angka">Rp2.185.000</p>
<p class="kartu-tren tren tren--naik tren--negatif">5,2% dari bulan lalu</p>
</article>
</section>
<section class="seksi-transaksi" id="transaksi" aria-labelledby="judul-transaksi">
<div class="seksi-kepala">
<h2 class="seksi-judul" id="judul-transaksi">Transaksi Terakhir</h2>
<a class="tautan-lain" href="#">Lihat semua transaksi</a>
</div>
<div class="tabel-gulir">
<table class="tabel-transaksi">
<caption class="sr-only">Delapan transaksi terakhir pada November 2026</caption>
<thead>
<tr>
<th scope="col">Tanggal</th>
<th scope="col">Keterangan</th>
<th scope="col" class="kolom-kategori">Kategori</th>
<th scope="col" class="kolom-jumlah">Jumlah</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr><td class="kolom-tanggal">18 Nov</td><td>Penjualan barang bekas</td><td class="kolom-kategori">Lainnya</td><td class="kolom-jumlah sel-masuk">+Rp420.000</td><td><span class="lencana-status lencana-status--masuk">Masuk</span></td></tr>
<tr><td class="kolom-tanggal">15 Nov</td><td>Ongkir belanja online</td><td class="kolom-kategori">Kebutuhan</td><td class="kolom-jumlah sel-keluar">−Rp35.000</td><td><span class="lencana-status lencana-status--keluar">Keluar</span></td></tr>
<tr><td class="kolom-tanggal">14 Nov</td><td>Makan siang kantor</td><td class="kolom-kategori">Makanan</td><td class="kolom-jumlah sel-keluar">−Rp275.000</td><td><span class="lencana-status lencana-status--keluar">Keluar</span></td></tr>
<tr><td class="kolom-tanggal">11 Nov</td><td>Tagihan listrik dan air</td><td class="kolom-kategori">Tagihan</td><td class="kolom-jumlah sel-keluar">−Rp486.000</td><td><span class="lencana-status lencana-status--keluar">Keluar</span></td></tr>
<tr><td class="kolom-tanggal">08 Nov</td><td>Proyek desain lepas</td><td class="kolom-kategori">Penghasilan</td><td class="kolom-jumlah sel-masuk">+Rp2.750.000</td><td><span class="lencana-status lencana-status--masuk">Masuk</span></td></tr>
<tr><td class="kolom-tanggal">05 Nov</td><td>Langganan streaming</td><td class="kolom-kategori">Hiburan</td><td class="kolom-jumlah sel-keluar">−Rp149.000</td><td><span class="lencana-status lencana-status--keluar">Keluar</span></td></tr>
<tr><td class="kolom-tanggal">03 Nov</td><td>Belanja bulanan supermarket</td><td class="kolom-kategori">Kebutuhan</td><td class="kolom-jumlah sel-keluar">−Rp1.240.000</td><td><span class="lencana-status lencana-status--keluar">Keluar</span></td></tr>
<tr><td class="kolom-tanggal">02 Nov</td><td>Gaji bulanan</td><td class="kolom-kategori">Penghasilan</td><td class="kolom-jumlah sel-masuk">+Rp8.500.000</td><td><span class="lencana-status lencana-status--masuk">Masuk</span></td></tr>
</tbody>
</table>
</div>
</section>
<p class="isi-kaki">Angka pada dasbor ini adalah contoh latihan; total pemasukan dikurangi pengeluaran cocok dengan saldo yang ditampilkan.</p>
</main>
</div>
</body>
</html>Ringkasan #
Tabel transaksi kini lengkap: semantiknya utuh, kepala kolomnya menempel saat digulir, angkanya rata kanan bertipografi tabular, dan statusnya berlencana berkontras baik. Seluruh komponen desktop sudah berdiri. Bagian 5 menutup proyek dengan media query keruntuhan ke bilah atas, JavaScript pembuka sidebar yang menjaga aria-expanded, serta penghormatan terhadap prefers-reduced-motion.