Design System Panduan visual dan komponen untuk LabaBersih v2 — 31 Maret 2026
Dokumen ini adalah satu-satunya referensi untuk semua keputusan visual di LabaBersih v2. Setiap rule ada karena masalah nyata dari v1 atau prinsip UX yang terbukti. Tujuannya bukan bikin "cantik" — tujuannya bikin Nelly (admin, 300 order/hari) dan Hafish (finance, cek laporan tiap pagi) bisa kerja cepat dan tanpa mikir.
1. Filosofi & Prinsip
Unified Surface
Pattern utama LabaBersih: satu container luar membungkus data terkait. Internal separation pakai divider tipis, bukan border/card terpisah. Ini mengurangi visual noise dan membuat data terasa sebagai satu kesatuan.
v1 punya masalah inkonsistensi — badge beda style di setiap halaman. Design system ini solve masalah itu dengan mendefinisikan satu sumber kebenaran untuk setiap komponen.
1 container, divider tipis antar row. Clean, kohesif.
Terlalu banyak border dan shadow. Visual noise. "AI banget".
Content-First
Setiap elemen di halaman harus punya alasan. Kalau gak bantu user selesaikan tugasnya — buang. Dashboard v1 punya 8 stat cards + chart + cash flow + alert. Hafish bilang: "saya buka dashboard, gak tau harus lihat apa dulu." v2: 4 stat cards saja. Less is more.
Mobile-First
Semua komponen dirancang untuk mobile terlebih dahulu, kemudian di-enhance untuk layar besar. Bottom nav di mobile, sidebar di desktop. Touch target minimal 44px.
Prinsip Inti
| Prinsip | Artinya untuk LabaBersih | Sumber |
|---|---|---|
| Consistency | 1 component = 1 style di SEMUA halaman. Badge "dikirim" harus warna yang sama di order, RTS, rekonsil. | Nielsen #4 |
| Recognition > Recall | Tampilkan nama produk + SKU, jangan suruh user hafal kode. Akun tampil nama, bukan cuma kode. | Nielsen #6 |
| 7 ± 2 items | Tabel max 7 kolom. Stat cards max 4 per row. Sidebar max 8 menu utama. | Miller's Law |
| Response < 400ms | Productivity naik drastis. Target TTFB < 300ms. Loading state kalau > 200ms. | Doherty Threshold |
| Don't Make Me Think | 5-second test: buka halaman, langsung tau ini apa dan mau ngapain. | Steve Krug |
2. Warna
Brand Green Palette
Warna utama LabaBersih: #00674F (green-600). Semua shade di-derive dari value ini. Di codebase, SELALU gunakan CSS variable / Tailwind token — jangan hardcode hex.
green-600 = primary. green-50 = hover background. green-700 = button hover.
Badge Tones (Emerald-Harmonic)
Warna badge lembut tapi tetap jelas perbedaannya. Background soft, text gelap. Bukan warna solid mencolok. Dibuat harmonis dengan brand green.
Status Mapping
Setiap status punya warna FIXED. Gak boleh beda antar halaman — ini yang bikin v1 "AI banget".
| Status | Tone | Rendered |
|---|---|---|
| Dibuat | Neutral | Dibuat |
| Dikemas | Info | Dikemas |
| Dikirim | Warning | Dikirim |
| Selesai | Success | Selesai |
| RTS | Error | RTS |
| Dibatalkan | Error | Dibatalkan |
| Lunas | Success | Lunas |
| Belum Bayar | Error | Belum Bayar |
Platform Colors
#ee4d2d#fff0ec#010101#00f2eaSpecial & Gray
#00674F#dc2626#e5e7eb#d5d7db#b0b3baCSS Variables
Kenapa token penting: kalau warna berubah, cukup ganti di 1 tempat. Kalau dark mode nanti, override di 1 tempat. Tanpa token = hardcode di 100 tempat = inkonsisten.
:root {
--green-600: #00674F; /* Primary brand */
--badge-success-bg: #BCF1DD; /* Badge: selesai, lunas */
--badge-error-bg: #FFD8D8; /* Badge: rts, error */
--badge-warning-bg: #FDDEB3; /* Badge: dikirim */
--badge-info-bg: #DFE3EA; /* Badge: dikemas */
--badge-neutral-bg: #E2E7E5; /* Badge: dibuat */
--profit: #00674F;
--loss: #dc2626;
}
3. Tipografi
Font
Geist (Google Fonts) untuk sans-serif. Geist Mono untuk monospace (ID, SKU, kode akun, angka uang). Fallback: system-ui, -apple-system, sans-serif. Mono fallback: SF Mono, Menlo, monospace.
Scale
Setiap level punya penggunaan spesifik. Jangan campur level — hierarchy harus jelas.
text-lg font-semibold text-gray-900
text-sm font-semibold text-gray-900
text-sm font-semibold text-gray-900
text-xs font-medium text-gray-500
text-2xl font-bold text-gray-900
text-sm text-gray-700
text-sm font-medium text-gray-700
text-xs text-gray-400
text-xs text-red-600
text-[13px] font-semibold
font-mono
text-[10px] font-semibold
Named Tokens (v2)
Gunakan nama semantik di code, bukan raw class. Contoh: bukan text-2xl font-bold, tapi wrap dalam component <.stat_value>.
| Token | Tailwind Class | Kapan |
|---|---|---|
page-title | text-lg font-semibold text-gray-900 | 1x per halaman, paling atas |
stat-value | text-2xl font-bold text-gray-900 | Angka di stat card |
stat-label | text-xs font-medium text-gray-500 | Label di stat card |
table-header | text-xs font-medium text-gray-500 | Header kolom tabel |
mono-id | font-mono text-sm text-gray-700 | ID, SKU, kode akun |
money | font-mono tabular-nums text-sm text-right | Semua angka uang |
4. Spacing & Layout
Spacing Constraints
Pakai Tailwind spacing scale (kelipatan 4px). JANGAN arbitrary values. Constraint = konsistensi.
App Shell
Desktop: sidebar kiri (w-60 = 240px), konten utama push kanan (lg:pl-60). Mobile: tanpa sidebar, bottom nav, konten full-width dengan pb-24 untuk clearance bottom nav.
Z-Index Scale
| Layer | Z-Index | Contoh |
|---|---|---|
| Base | 0 | Konten utama |
| Dropdown | 10 | Combobox list, tooltip |
| Sticky | 20 | Table header, form footer |
| Sidebar | 30 | Desktop sidebar |
| Overlay | 40 | Modal backdrop |
| Modal | 50 | Dialog content |
| Toast | 60 | Notification flash |
Touch Rules
Minimum touch target: 44px. Gunakan touch-action: manipulation pada semua interactive elements. Viewport: maximum-scale=1, user-scalable=no untuk mencegah zoom tidak sengaja pada form.
5. Border, Radius & Shadow
Radius
Container
Controls
Badge
Logo
Shadow
Card default
Dropdown
Modal
Flat card
Border & Divider
gray-200 hanya untuk emphasis divider atau border yang butuh kontras lebih (jarang dipakai).
6a. Button
4 varian, 2 ukuran. Primary untuk aksi utama, secondary untuk aksi pendukung, danger untuk destructive, ghost untuk aksi minor tanpa emphasis.
Placement Rules
Primary (Simpan) selalu di kanan. Cancel (Batal) di kiri primary. Danger (Hapus) di far left, terpisah jauh dari primary — Fitts's Law.
6b. Badge
<!-- Badge inline style pattern -->
<span style="
display:inline-flex;align-items:center;
padding:2px 8px;border-radius:999px;
font-size:13px;font-weight:600;
background:#BCF1DD;color:#004E38;
">Selesai</span>
6c. Card
6d. CollapsibleCard
Klik header untuk toggle konten. Chevron rotate 180 derajat. Gunakan untuk detail yang gak selalu perlu dilihat.
6e. Stats Row
divide-x dalam 1 container, bukan card terpisah.6f. Segmented Control
Container: bg-gray-100 rounded-lg p-1. Active tab: bg-white shadow-sm. Inactive: text-gray-500.
6g. Unified Surface (Filter + Table)
grid-cols-[...]. Ini memberikan kontrol lebih untuk responsive dan styling.6h. Form & Input
Input Variants
6i. SearchableCombobox
Active item: bg-green-50 text-green-700. Keyboard: Arrow up/down, Enter select, Escape close.
6j. Toggle
6k. ConfirmModal
6l. Empty & Loading State
6m. Save Indicator & Tooltip
Hover pada (?) untuk melihat tooltip. Di production, gunakan CSS :hover selector.
6n. Error States
6o. Infinite Scroll
6p. Offline Banner
6q. DateRangeFilter
Date picker dengan preset shortcuts (Hari Ini, 7 Hari, 30 Hari, Bulan Ini) + custom date range. HANYA untuk daftar pendek (< 8 items) pakai Select biasa. Untuk range tanggal, pakai component ini.
Classes (v1 reference):
Trigger closed: flex items-center gap-2 rounded-lg border border-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50
Trigger open: border-green-500 ring-2 ring-green-500/20
Dropdown: absolute z-20 mt-1.5 w-80 rounded-xl border border-gray-100 bg-white shadow-lg
Active preset: bg-green-50 font-medium text-green-700
Inactive: text-gray-700 hover:bg-gray-50
Date input: rounded-lg border border-gray-200 px-2.5 py-1.5 text-xs focus:border-green-500 focus:ring-2 focus:ring-green-500/20
6r. MetricCardChart (Marketing)
Digunakan di halaman marketing — BUKAN di list page (list page pakai Stats Row). Pattern: grid metric cards yang bisa diklik (max 2 aktif), connected ke area chart dual Y-axis.
invertDelta. CPC naik 8.7% → merah, karena biaya naik itu negatif.
Pattern:
Active card: border-color: {metric.color}40, bg: {metric.color}08, L/R badge
Inactive card: border-gray-100 bg-white
Delta badge: text-[10px] font-semibold, green=good red=bad, invertDelta untuk cost
Format: rp → "Rp 1,2jt", persen → "12.5%", angka → locale int
7. Ikon
Stroke-based SVG, Heroicons style. viewBox="0 0 24 24", stroke-width="1.5" atau "2". Gunakan currentColor supaya warna ikut text color parent.
8. Pola Keuangan (Accounting UI)
Pattern spesifik untuk halaman akuntansi. Semua angka pakai font-mono tabular-nums. Negatif = text-red-600. Nol = em dash "—". Referensi: Xero, QuickBooks, A2X, Zoho Books.
8a. Laporan Keuangan — Report Tree
Laporan Laba Rugi dan Neraca menggunakan tree hierarchy. Group header collapsible, child rows indented, total rows bold dengan border atas tebal.
8b. Bagan Akun — Tree View
8c. Buku Besar — Running Balance
8d. Rekonsiliasi — Fee Breakdown
8e. Konsolidasi Multi-Entity
8f. Format Angka
9. Pola Halaman
8a. List Page
Urutan FIXED: Header → Stats Row → Segmented Control → Filter Bar → Table → Pagination. Jangan dibolak-balik.
8b. Detail Page
Back nav → Header card (title + actions) → Info grid (2-3 kolom) → Detail sections → Activity timeline.
8c. Form Page
PageLayout → Card dengan form fields (grid 2 kolom) → Sticky footer: [Hapus] ← | → [Batal] [Simpan].
Untuk form panjang yang bisa discroll, footer sticky di bawah memastikan user selalu bisa akses action button tanpa scroll.
10. Navigasi
9a. Desktop Sidebar
Active nav item: bg-green-50/60 text-green-700 font-medium. Expandable sub-items: pl-11 indent.
9b. Mobile Bottom Nav
11. Interaksi
SEMUA elemen interaktif harus punya transition-colors (duration 150ms). Jangan pernah ada perubahan warna yang "instan".
Hover States
| Element | Normal | Hover |
|---|---|---|
| Primary button | bg-green-600 | bg-green-700 |
| Secondary button | bg-white border-gray-200 | bg-gray-50 |
| Table row | bg-transparent | bg-gray-50 |
| Nav link | text-gray-500 | bg-gray-50 text-gray-900 |
| Ghost button | text-green-600 | underline |
Focus States
Input focus: ring-2 ring-green-600/20 border-green-400. Error focus: ring-2 ring-red-600/10 border-red-500. Button focus: ring-2 ring-green-600/20.
Disabled
opacity-50 cursor-not-allowed pointer-events-none. Berlaku untuk semua element interaktif.
12. Responsif
| Breakpoint | Width | Layout |
|---|---|---|
| Default (mobile) | < 640px | Full width, bottom nav, stack semua |
sm | ≥ 640px | 2-col form grid |
lg | ≥ 1024px | Sidebar visible, lg:pl-60 |
Pattern Umum
hidden sm:block— sembunyikan di mobile, tampil di sm+sm:hidden— tampil di mobile, sembunyikan di sm+lg:pl-60— push konten untuk sidebar di desktoppb-24 lg:pb-8— clearance bottom nav di mobile, normal di desktop
13. Lokalisasi
| Aspek | Aturan | Contoh |
|---|---|---|
| Bahasa | Bahasa Indonesia | "Simpan", "Batal", "Pesanan" |
| Angka | Locale id-ID, titik pemisah ribuan | 123.456 |
| Mata uang | "Rp " + angka | Rp 150.000 |
| Mata uang pendek | Jutaan disingkat | Rp 1,2jt |
| Tanggal | DD MMM YYYY | 25 Mar 2026 |
| Bulan | Jan, Feb, Mar, Apr, Mei, Jun, Jul, Ags, Sep, Okt, Nov, Des | |
14. Aksesibilitas
Polaris-level accessibility. Bukan "nice to have" — ini wajib. Terutama untuk keyboard navigation karena Nelly sering pakai barcode scanner yang mengirim input sebagai keyboard event.
| Aturan | Implementasi |
|---|---|
aria-label pada icon-only button | Semua button tanpa text WAJIB punya aria-label |
role="alert" pada error | Error message, toast, flash — supaya screen reader baca langsung |
| Focus trap di modal | Tab cycle dalam modal, tidak keluar ke background |
| Focus visible ring | Keyboard focus: ring-2 ring-offset-2 ring-green-600 |
| Keyboard nav | Tab = next, Shift+Tab = prev, Enter = activate, Escape = close/cancel |
| Contrast ratio | WCAG AA minimum: 4.5:1 untuk text, 3:1 untuk large text |
<button aria-label="Hapus pesanan"><TrashIcon /></button>
<button><TrashIcon /></button>
15. Aturan Wajib
Do's
Don'ts
16. 18 Keputusan Kunci
Setiap keputusan di bawah sudah final. Jangan deviate tanpa persetujuan Hafish.
- Unified Surface > Card-per-Row. Mengurangi visual noise. Data yang terkait harus dalam 1 container dengan divider tipis.
- Stats selalu 4 kolom, bukan 3. Konsistensi di semua halaman. Kalau data cuma 3, tambah 1 total/placeholder.
- "Batal" bukan "Kembali" di form. "Kembali" ambigu (navigasi?). "Batal" jelas: cancel action ini.
- Cancel = secondary button, bukan ghost. Ghost terlalu subtle. User harus bisa menemukan cancel dengan mudah.
- Toggle ON = gray-900, bukan green. Green reserved untuk brand action. Toggle cukup on/off visual.
- Auto-save indicator = gray. Informational, bukan action. Gak perlu warna mencolok.
- Delta coloring: invertDelta untuk cost metrics. Biaya naik = merah (buruk). Revenue naik = hijau (bagus). Konteks menentukan warna.
- CSS Grid > HTML table. Kontrol lebih baik untuk responsive, alignment, dan custom styling.
- Sticky footer untuk form panjang. User selalu bisa akses Simpan/Batal tanpa scroll ke bawah.
- Badge warna via inline style. Menjamin konsistensi di semua konteks (SSR, dark mode, PDF export).
- touch-action: manipulation. Disable double-tap zoom di semua interactive elements. Penting untuk barcode scanner.
- Viewport: maximumScale=1, userScalable=false. Mencegah zoom tidak sengaja di form input pada iOS.
- Font: Geist. Modern, clean, excellent readability. Monospace variant untuk angka dan ID.
- Max 7 kolom tabel. Miller's Law. Sisanya di detail page.
- Bahasa Indonesia 100%. Label, placeholder, error message, tooltip — semua Bahasa Indonesia.
- WIB timezone. Semua datetime tampil dalam WIB (+07:00), bukan UTC.
- Money format: "Rp " + titik ribuan. Negatif = "-Rp". Warna merah untuk negatif, hijau untuk positif (kalau pakai sign).
- Error message = bahasa user. "Stok tidak cukup (ada: 3, butuh: 5)" bukan "constraint violation".
17. Logo
Logo LabaBersih: Green square "L" + gold coin "$". SVG, rx="8" pada square. Render di 3 ukuran.