Dasbor yang nyaman di monitor 1440px bisa menyusut jadi deretan kartu yang tumpang-tindih pada tablet, dan menjadi sisa sidebar yang memakan separuh layar pada ponsel. Bagian penutup ini melengkapi styles/dashboard.css dengan dua media query: pada 1023px sidebar berubah menjadi rail ikon selebar 72px, pada 767px sidebar ditarik sepenuhnya dan konten memakai satu kolom. Kita juga menutup dua celah kecil yang sering luput — tinggi viewport yang berubah-ubah di perangkat mobile dan tabel yang lebih lebar daripada layarnya.
Tujuan Bagian dan Prasyarat #
Target bagian ini:
.tabel-gulir: pembungkus tabel denganoverflow-x: autoagar baris data bergulir, bukan terpotong..dasborbertinggi100dvhdengan fallback100vh.- Breakpoint 1023px: kolom grid
72px 1fr, label teks disembunyikan, ringkasan dua kolom, padding konten 24px. - Breakpoint 767px: satu kolom penuh, sidebar
display: none, kartu menumpuk, judul bar atas turun ke 16px.
Konsep yang kita andalkan: Media Queries
, Breakpoints
, Viewport Units
untuk dvh, dan Testing Berbagai Viewport
untuk kebiasaan memeriksa hasil.
Gulir Horizontal dan Tinggi Dinamis #
Dua aturan pendek ditambahkan di awal blok Bagian 4:
/* Pembungkus tabel agar baris data lebar tetap terbaca
dengan gulir horizontal, bukan terpotong */
.tabel-gulir {
overflow-x: auto;
}
.dasbor {
/* Fallback 100vh diikuti 100dvh untuk browser yang
mendukung dynamic viewport height (Baseline 2023) */
height: 100vh;
height: 100dvh;
}Di markup, panel tabel dibungkus .tabel-gulir, dan label sidebar dibungkus span agar bisa disembunyikan media query nanti:
<div class="tabel-gulir">
<table class="tabel">
<!-- isi tabel pesanan dari Bagian 3 -->
</table>
</div>
<aside class="sidebar">
<a class="logo-dasbor" href="#">
<img class="logo-toko" src="assets/logo-toko.svg" width="32" height="32" alt="Logo Toko Kita">
<span class="logo-teks">Toko Kita</span>
</a>
<ul class="menu">
<li><a class="menu-item menu-item-aktif" href="#"><span class="menu-ikon"></span><span class="menu-teks">Beranda</span></a></li>
<li><a class="menu-item" href="#"><span class="menu-ikon"></span><span class="menu-teks">Pesanan</span></a></li>
</ul>
</aside>Hasil di Browser:
.tabel-gulir bernilai overflow-x auto, dan tinggi .dasbor
terresolusi menjadi 720px pada viewport 720px — 100dvh
menimpa 100vh dengan nilai yang sama di lingkungan desktop.Dua Breakpoint: Rail dan Satu Kolom #
Seluruh perubahan responsif ditumpuk di akhir file:
@media (max-width: 1023px) {
.dasbor {
grid-template-columns: 72px 1fr;
}
.logo-teks,
.menu-teks {
display: none;
}
.menu-item {
justify-content: center;
}
.ringkasan {
grid-template-columns: repeat(2, 1fr);
}
.utama {
padding: var(--spasi-6);
}
}
@media (max-width: 767px) {
.dasbor {
grid-template-columns: 1fr;
grid-template-areas:
"atas"
"utama";
}
.sidebar {
display: none;
}
.ringkasan {
grid-template-columns: 1fr;
}
.atas-judul {
font-size: 16px;
}
}Bedah Aturan Kunci #
- Urutan fallback
100vhlalu100dvh: deklarasi kedua berlaku hanya bila browser mengenaldvh; bila tidak, deklarasi itu dibuang dan100vhtetap memimpin. Unitdvhmengikuti tinggi nyata setelah bilah alamat browser mobile menyusut atau muncul. - Rail 72px di 1023px: kolom pertama disempitkan, label teks
display: none, danjustify-content: centermerapatkan ikon di tengah rail. Karena grid area tetap sama, tidak ada markup yang dipindah. - Satu kolom di 767px:
grid-template-areasdideklarasikan ulang tanpa kolom sidebar, lalu.sidebarditarik dari alur dengandisplay: none. Ringkasan menumpuk menjadi satu kartu selebar penuh. - Desktop-first sebagai keputusan: dasbor didesain untuk layar lebar, jadi media query
max-widthmenurunkan layout secara bertahap. Chapter Mobile-First mengulas strategi sebaliknya — keduanya sah, tetapi satu proyek harus konsisten memilih satu. - Angka desktop pada halaman uji: viewport harness 1280px berada di atas kedua breakpoint, sehingga semua probe mengukur kondisi sebelum media query bekerja.
Uji Bagian Ini #
Halaman uji bagian ini tersedia di /tests/css/proyek/dashboard-admin/responsive-dashboard.html
, dengan spesifikasi lengkap pada tests/responsive-dashboard.spec.js:
| Elemen | Properti | Diharapkan |
|---|---|---|
.tabel-gulir | overflow-x | auto |
.dasbor | height | 720px |
.dasbor | grid-template-columns | 260px 1020px |
.sidebar | display | flex |
.sidebar | width | 260px |
.menu-teks | display | block |
.ringkasan | grid-template-columns | 221px 221px 221px 221px |
.utama | padding-top | 32px |
.atas-judul | font-size | 18px |
Dengan bagian ini selesai, styles/dashboard.css utuh dari token hingga breakpoint dan seluruh halaman uji proyek dapat dijalankan dengan node scripts/css-test/run_tests.mjs --filter dashboard-admin.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
styles/dashboard.css
Unduh berkas/* =========================================================
Proyek: Dashboard Admin — styles/dashboard.css
File utuh proyek yang berkembang mengikuti empat bagian:
Bagian 1 (Dashboard Layout), Bagian 2 (Sidebar dan
Navigation), Bagian 3 (Data Cards dan Tables), Bagian 4
(Responsive Dashboard). Komentar "Bagian N" menandai
kelompok aturan yang dibahas pada setiap bagian.
========================================================= */
/* Bagian 1: Dashboard Layout ------------------------------ */
:root {
/* Warna semantik — dasbor toko daring Toko Kita */
--warna-primer: #4f46e5;
--warna-primer-tua: #4338ca;
--warna-latar: #f5f5f4;
--warna-permukaan: #ffffff;
--warna-teks: #1c1917;
--warna-teks-lembut: #78716c;
--warna-teks-balik: #ffffff;
--warna-garis: #e7e5e4;
--warna-sidebar: #1c1917;
--warna-sidebar-teks: #d6d3d1;
--warna-sidebar-aktif: #44403c;
/* Skala spasi */
--spasi-1: 4px;
--spasi-2: 8px;
--spasi-3: 12px;
--spasi-4: 16px;
--spasi-6: 24px;
--spasi-8: 32px;
/* Bentuk */
--radius-sedang: 10px;
--radius-kartu: 12px;
--radius-penuh: 999px;
/* Dimensi kerangka */
--font-utama: system-ui, sans-serif;
--lebar-sidebar: 260px;
--tinggi-atas: 64px;
}
* {
margin: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-utama);
font-size: 16px;
line-height: 1.6;
color: var(--warna-teks);
background-color: var(--warna-latar);
}
.dasbor {
display: grid;
grid-template-columns: var(--lebar-sidebar) 1fr;
grid-template-rows: var(--tinggi-atas) 1fr;
grid-template-areas:
"sidebar atas"
"sidebar utama";
/* Bagian 4: menambah baris height: 100dvh sebagai pengganti */
height: 100vh;
}
.sidebar {
grid-area: sidebar;
}
.atas {
grid-area: atas;
}
.utama {
grid-area: utama;
overflow-y: auto;
padding: var(--spasi-8);
}
/* Bagian 2: Sidebar dan Navigation ------------------------ */
.sidebar {
display: flex;
flex-direction: column;
gap: var(--spasi-6);
padding: var(--spasi-6);
background-color: var(--warna-sidebar);
color: var(--warna-sidebar-teks);
}
.logo-dasbor {
display: flex;
align-items: center;
gap: var(--spasi-3);
color: var(--warna-teks-balik);
font-weight: 700;
font-size: 18px;
text-decoration: none;
}
.logo-toko {
width: 32px;
height: 32px;
}
.menu {
display: flex;
flex-direction: column;
gap: var(--spasi-2);
list-style: none;
}
.menu-item {
display: flex;
align-items: center;
gap: var(--spasi-3);
padding: var(--spasi-3) var(--spasi-4);
border-radius: var(--radius-sedang);
color: var(--warna-sidebar-teks);
text-decoration: none;
font-size: 14px;
font-weight: 500;
}
.menu-item:hover {
background-color: rgb(255 255 255 / 8%);
}
.menu-item-aktif {
background-color: var(--warna-sidebar-aktif);
color: var(--warna-teks-balik);
}
.menu-ikon {
flex: none;
width: 8px;
height: 8px;
border-radius: var(--radius-penuh);
background-color: currentColor;
}
.atas {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spasi-6);
padding: 0 var(--spasi-8);
background-color: var(--warna-permukaan);
border-bottom: 1px solid var(--warna-garis);
}
.atas-judul {
font-size: 18px;
font-weight: 600;
}
.atas-aksi {
display: flex;
align-items: center;
gap: var(--spasi-4);
}
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: var(--warna-primer);
color: var(--warna-teks-balik);
font-size: 13px;
font-weight: 600;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Bagian 3: Data Cards dan Tables ------------------------- */
.ringkasan {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--spasi-6);
margin-bottom: var(--spasi-8);
}
.kartu-stat {
background-color: var(--warna-permukaan);
border: 1px solid var(--warna-garis);
border-radius: var(--radius-kartu);
padding: var(--spasi-6);
}
.kartu-label {
font-size: 14px;
color: var(--warna-teks-lembut);
}
.kartu-nilai {
margin-top: var(--spasi-2);
font-size: 28px;
font-weight: 700;
}
.kartu-perubahan {
margin-top: var(--spasi-2);
font-size: 13px;
color: #166534;
}
.kartu-perubahan-turun {
color: #991b1b;
}
.panel {
background-color: var(--warna-permukaan);
border: 1px solid var(--warna-garis);
border-radius: var(--radius-kartu);
padding: var(--spasi-6);
}
.judul-panel {
font-size: 18px;
font-weight: 600;
}
.tabel {
width: 100%;
border-collapse: collapse;
margin-top: var(--spasi-4);
}
.tabel th {
text-align: left;
padding: var(--spasi-3) var(--spasi-4);
border-bottom: 1px solid var(--warna-garis);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.4px;
color: var(--warna-teks-lembut);
}
.tabel td {
padding: var(--spasi-4);
border-bottom: 1px solid var(--warna-garis);
font-size: 14px;
}
.status {
display: inline-block;
padding: var(--spasi-1) var(--spasi-3);
border-radius: var(--radius-penuh);
font-size: 12px;
font-weight: 500;
}
.status-lunas {
background-color: #dcfce7;
color: #166534;
}
.status-diproses {
background-color: #fef3c7;
color: #92400e;
}
.status-dibatalkan {
background-color: #fee2e2;
color: #991b1b;
}
/* Bagian 4: Responsive Dashboard -------------------------- */
/* Pembungkus tabel agar baris data lebar tetap terbaca
dengan gulir horizontal, bukan terpotong */
.tabel-gulir {
overflow-x: auto;
}
.dasbor {
/* Fallback 100vh diikuti 100dvh untuk browser yang
mendukung dynamic viewport height (Baseline 2023) */
height: 100vh;
height: 100dvh;
}
@media (max-width: 1023px) {
.dasbor {
grid-template-columns: 72px 1fr;
}
.logo-teks,
.menu-teks {
display: none;
}
.menu-item {
justify-content: center;
}
.ringkasan {
grid-template-columns: repeat(2, 1fr);
}
.utama {
padding: var(--spasi-6);
}
}
@media (max-width: 767px) {
.dasbor {
grid-template-columns: 1fr;
grid-template-areas:
"atas"
"utama";
}
.sidebar {
display: none;
}
.ringkasan {
grid-template-columns: 1fr;
}
.atas-judul {
font-size: 16px;
}
}
tests/dashboard-layout.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 1: Dashboard Layout
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
// /tests/css/proyek/dashboard-admin/dashboard-layout.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// body: tipografi dasar dan warna dari token semantik.
{ elemen: "body", properti: "font-size", diharapkan: "16px" },
{ elemen: "body", properti: "line-height", diharapkan: "25.6px" },
{ elemen: "body", properti: "color", diharapkan: "rgb(28, 25, 23)" },
{ elemen: "body", properti: "background-color", diharapkan: "rgb(245, 245, 244)" },
// .dasbor: grid dua kolom dua baris dengan grid-template-areas.
{ elemen: ".dasbor", properti: "display", diharapkan: "grid" },
{ elemen: ".dasbor", properti: "height", diharapkan: "720px" },
{ elemen: ".dasbor", properti: "grid-template-columns", diharapkan: "260px 1020px" },
{ elemen: ".dasbor", properti: "grid-template-rows", diharapkan: "64px 656px" },
{ elemen: ".dasbor", properti: "grid-template-areas", diharapkan: "\"sidebar atas\" \"sidebar utama\"" },
// .sidebar: kolom pertama selebar token --lebar-sidebar.
{ elemen: ".sidebar", properti: "grid-area", diharapkan: "sidebar" },
{ elemen: ".sidebar", properti: "width", diharapkan: "260px" },
// .atas: baris pertama kolom kedua, tinggi token --tinggi-atas.
{ elemen: ".atas", properti: "grid-area", diharapkan: "atas" },
{ elemen: ".atas", properti: "width", diharapkan: "1020px" },
// .utama: area konten yang bergulir sendiri dengan padding token.
{ elemen: ".utama", properti: "grid-area", diharapkan: "utama" },
{ elemen: ".utama", properti: "overflow-y", diharapkan: "auto" },
{ elemen: ".utama", properti: "padding-left", diharapkan: "32px" },
];
tests/data-cards-dan-tables.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 3: Data Cards dan Tables
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
// /tests/css/proyek/dashboard-admin/data-cards-dan-tables.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// .ringkasan: grid empat kolom sama lebar dengan gap token spasi-6.
{ elemen: ".ringkasan", properti: "display", diharapkan: "grid" },
{ elemen: ".ringkasan", properti: "grid-template-columns", diharapkan: "221px 221px 221px 221px" },
{ elemen: ".ringkasan", properti: "gap", diharapkan: "24px" },
{ elemen: ".ringkasan", properti: "margin-bottom", diharapkan: "32px" },
// .kartu-stat: permukaan putih, border token, radius kartu 12px.
{ elemen: ".kartu-stat", properti: "padding-top", diharapkan: "24px" },
{ elemen: ".kartu-stat", properti: "border-radius", diharapkan: "12px" },
{ elemen: ".kartu-stat", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
{ elemen: ".kartu-stat", properti: "border-top-color", diharapkan: "rgb(231, 229, 228)" },
// Tiga baris isi kartu: label lembut, nilai besar, perubahan naik.
{ elemen: ".kartu-label", properti: "font-size", diharapkan: "14px" },
{ elemen: ".kartu-label", properti: "color", diharapkan: "rgb(120, 113, 108)" },
{ elemen: ".kartu-nilai", properti: "font-size", diharapkan: "28px" },
{ elemen: ".kartu-nilai", properti: "font-weight", diharapkan: "700" },
{ elemen: ".kartu-perubahan", properti: "font-size", diharapkan: "13px" },
{ elemen: ".kartu-perubahan", properti: "color", diharapkan: "rgb(22, 101, 52)" },
// .panel dan .judul-panel: wadah tabel dengan judul 18px.
{ elemen: ".panel", properti: "padding-top", diharapkan: "24px" },
{ elemen: ".panel", properti: "border-radius", diharapkan: "12px" },
{ elemen: ".judul-panel", properti: "font-size", diharapkan: "18px" },
{ elemen: ".judul-panel", properti: "font-weight", diharapkan: "600" },
// .tabel: border antarsel dilebur; deklarasi width 100% terresolusi
// menjadi lebar terpakai 906px di dalam panel (956px dikurangi
// padding 24px dua sisi dan 2px border).
{ elemen: ".tabel", properti: "border-collapse", diharapkan: "collapse" },
{ elemen: ".tabel", properti: "width", diharapkan: "906px" },
// .tabel th: header kapital dengan pelacakan huruf longgar.
{ elemen: ".tabel th", properti: "text-align", diharapkan: "left" },
{ elemen: ".tabel th", properti: "text-transform", diharapkan: "uppercase" },
{ elemen: ".tabel th", properti: "font-size", diharapkan: "13px" },
{ elemen: ".tabel th", properti: "letter-spacing", diharapkan: "0.4px" },
{ elemen: ".tabel th", properti: "padding-top", diharapkan: "12px" },
{ elemen: ".tabel th", properti: "color", diharapkan: "rgb(120, 113, 108)" },
// .tabel td: sel data berpadding 16px dengan garis bawah tipis.
{ elemen: ".tabel td", properti: "padding", diharapkan: "16px" },
{ elemen: ".tabel td", properti: "font-size", diharapkan: "14px" },
{ elemen: ".tabel td", properti: "border-bottom-width", diharapkan: "1px" },
// .status-lunas: pill hijau latar muda dengan teks hijau tua.
{ elemen: ".status-lunas", properti: "background-color", diharapkan: "rgb(220, 252, 231)" },
{ elemen: ".status-lunas", properti: "color", diharapkan: "rgb(22, 101, 52)" },
{ elemen: ".status-lunas", properti: "border-radius", diharapkan: "999px" },
{ elemen: ".status-lunas", properti: "font-size", diharapkan: "12px" },
];
tests/responsive-dashboard.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 4: Responsive Dashboard
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
// /tests/css/proyek/dashboard-admin/responsive-dashboard.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan. Harness membuka halaman
// pada viewport 1280x720, sehingga kedua media query
// (max-width: 1023px dan max-width: 767px) berstatus tidak
// aktif — asersi di bawah membuktikan kondisi desktop sebelum
// breakpoint bekerja.
const asersi = [
// .tabel-gulir: pembungkus tabel dengan gulir horizontal sendiri.
{ elemen: ".tabel-gulir", properti: "overflow-x", diharapkan: "auto" },
// .dasbor: 100dvh terresolusi menjadi tinggi viewport penuh.
{ elemen: ".dasbor", properti: "height", diharapkan: "720px" },
// Kondisi desktop: sidebar 260px dan grid tetap dua kolom.
{ elemen: ".dasbor", properti: "grid-template-columns", diharapkan: "260px 1020px" },
{ elemen: ".sidebar", properti: "display", diharapkan: "flex" },
{ elemen: ".sidebar", properti: "width", diharapkan: "260px" },
// .menu-teks: label menu terlihat (flex item) di desktop,
// disembunyikan media query 1023px menjadi rail 72px.
{ elemen: ".menu-teks", properti: "display", diharapkan: "block" },
// Kondisi desktop yang berubah di breakpoint: empat kartu
// 221px, padding konten 32px, judul bar atas 18px.
{ elemen: ".ringkasan", properti: "grid-template-columns", diharapkan: "221px 221px 221px 221px" },
{ elemen: ".utama", properti: "padding-top", diharapkan: "32px" },
{ elemen: ".atas-judul", properti: "font-size", diharapkan: "18px" },
];
tests/sidebar-dan-navigation.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 2: Sidebar dan Navigation
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
// /tests/css/proyek/dashboard-admin/sidebar-dan-navigation.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// .sidebar: flex column dengan latar gelap dan jarak antarblok 24px.
{ elemen: ".sidebar", properti: "display", diharapkan: "flex" },
{ elemen: ".sidebar", properti: "flex-direction", diharapkan: "column" },
{ elemen: ".sidebar", properti: "gap", diharapkan: "24px" },
{ elemen: ".sidebar", properti: "padding-top", diharapkan: "24px" },
{ elemen: ".sidebar", properti: "background-color", diharapkan: "rgb(28, 25, 23)" },
// .logo-dasbor: nama toko dengan teks putih tebal 18px.
{ elemen: ".logo-dasbor", properti: "font-size", diharapkan: "18px" },
{ elemen: ".logo-dasbor", properti: "font-weight", diharapkan: "700" },
{ elemen: ".logo-dasbor", properti: "color", diharapkan: "rgb(255, 255, 255)" },
// .menu: daftar vertikal berjarak 8px antartautan.
{ elemen: ".menu", properti: "display", diharapkan: "flex" },
{ elemen: ".menu", properti: "flex-direction", diharapkan: "column" },
{ elemen: ".menu", properti: "gap", diharapkan: "8px" },
// .menu-item-aktif: pill terang dengan teks putih di atas menu gelap.
{ elemen: ".menu-item", properti: "gap", diharapkan: "12px" },
{ elemen: ".menu-item", properti: "padding-top", diharapkan: "12px" },
{ elemen: ".menu-item", properti: "border-radius", diharapkan: "10px" },
{ elemen: ".menu-item", properti: "font-size", diharapkan: "14px" },
{ elemen: ".menu-item-aktif", properti: "color", diharapkan: "rgb(255, 255, 255)" },
{ elemen: ".menu-item-aktif", properti: "background-color", diharapkan: "rgb(68, 64, 60)" },
{ elemen: ".menu-item-aktif .menu-ikon", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
// .atas: baris atas setinggi 64px dengan ruas kiri-kanan terpisah.
{ elemen: ".atas", properti: "height", diharapkan: "64px" },
{ elemen: ".atas", properti: "justify-content", diharapkan: "space-between" },
{ elemen: ".atas", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
{ elemen: ".atas", properti: "border-bottom-color", diharapkan: "rgb(231, 229, 228)" },
// .atas-judul: judul halaman 18px ketebalan 600.
{ elemen: ".atas-judul", properti: "font-size", diharapkan: "18px" },
{ elemen: ".atas-judul", properti: "font-weight", diharapkan: "600" },
// .avatar: lingkaran primer 36px di ruas kanan bar atas.
{ elemen: ".avatar", properti: "width", diharapkan: "36px" },
{ elemen: ".avatar", properti: "height", diharapkan: "36px" },
{ elemen: ".avatar", properti: "border-radius", diharapkan: "50%" },
{ elemen: ".avatar", properti: "background-color", diharapkan: "rgb(79, 70, 229)" },
];