๐ Dark Mode Pairing Guide
Purpose: Systematic mapping light tokens โ dark tokens untuk expand dark mode coverage dari 2 โ 42 screens.
Principle: Dark mode bukan sekadar "invert warna". Tiap kategori token punya pairing rule yang berbeda.
๐ฏ CORE PRINCIPLESโ
1. Dark โ Invertedโ
โ Jangan lakukan:
light: #FFFFFF (white) โ dark: #000000 (black)
light: #212121 (dark text) โ dark: #FFFFFF (white text)
Ini lazy mapping yang menghasilkan high contrast mencolok = eye strain.
โ Lakukan:
light: #FFFFFF โ dark: #18181B (zinc-900, soft dark)
light: #18181B โ dark: #FAFAFA (zinc-50, soft white)
2. Elevation terbalikโ
Light mode: Elevated = lebih putih (shadow-driven) Dark mode: Elevated = lebih terang (surface-driven)
Light:
background: #FAFAFA (base)
card: #FFFFFF (elevated via shadow)
modal: #FFFFFF (higher shadow)
Dark:
background: #09090B (base, darkest)
card: #18181B (slightly lighter)
modal: #27272A (lightest, border-accented)
3. Brand colors perlu tuningโ
Emerald-600 (#059669) di light mode kelihatan bagus di atas white. Tapi di dark mode, ini kurang kontras dengan background gelap. Perlu shift ke emerald-500 (#10B981) atau emerald-400 untuk interactive elements.
4. Colored backgrounds: reduce saturationโ
Light mode pakai emerald-50 (#ECFDF5) sebagai success bg. Di dark mode, jangan pakai emerald-900 (terlalu pekat) โ pakai alpha transparency over surface:
Light: bg: emerald-50 (#ECFDF5)
Dark: bg: rgba(16, 185, 129, 0.15) // emerald-500 at 15% alpha
5. Shadows kurang efektif di dark modeโ
Ganti strategi:
- Light mode: shadow untuk elevation
- Dark mode: border accent atau background lighter untuk elevation
๐ COMPLETE TOKEN PAIRING TABLEโ
SURFACE TOKENSโ
| Token name | Light | Dark | Notes |
|---|---|---|---|
surface/background | #FAFAFA (stone-50) | #09090B (zinc-950) | Hindari pure black |
surface/card | #FFFFFF (white) | #18181B (zinc-900) | Primary surface |
surface/card-hover | #FAFAFA (zinc-50) | #27272A (zinc-800) | Interactive |
surface/variant | #F4F4F5 (zinc-100) | #27272A (zinc-800) | Subtle container |
surface/elevated | #FFFFFF + shadow | #27272A (no shadow) | Modal/dropdown |
surface/overlay | rgba(0,0,0,0.5) | rgba(0,0,0,0.7) | Deeper dim di dark |
surface/appbar | #FFFFFF (solid) | #09090B + blur | Dark: glass effect |
surface/bottomtab | #FFFFFF | #18181B | Bottom nav |
surface/bottomtab-active | rgba(5,150,105,0.08) | rgba(16,185,129,0.15) | Active tab tint |
TEXT TOKENSโ
| Token name | Light | Dark | Notes |
|---|---|---|---|
text/primary | #18181B (zinc-900) | #FAFAFA (zinc-50) | Headline, body |
text/secondary | #52525B (zinc-600) | #A1A1AA (zinc-400) | Subtext, meta |
text/tertiary | #71717A (zinc-500) | #71717A (zinc-500) | Hints, placeholders (same both!) |
text/disabled | #D4D4D8 (zinc-300) | #52525B (zinc-600) | Inverted logic |
text/inverse | #FFFFFF | #18181B | Untuk text di atas brand solid |
text/link | #059669 (emerald-600) | #34D399 (emerald-400) | Links brighter di dark |
text/on-primary | #FFFFFF | #FFFFFF | Selalu white pada brand |
BRAND TOKENSโ
| Token name | Light | Dark | Notes |
|---|---|---|---|
brand/primary | #059669 (emerald-600) | #10B981 (emerald-500) | Interactive brighter di dark |
brand/primary-hover | #047857 (emerald-700) | #34D399 (emerald-400) | Hover inverted direction |
brand/primary-pressed | #065F46 (emerald-800) | #059669 (emerald-600) | Pressed darker di light, lighter di dark |
brand/primary-light | #ECFDF5 (emerald-50) | rgba(16,185,129,0.15) | Tint background |
brand/secondary | #0D9488 (teal-600) | #14B8A6 (teal-500) | Same pattern |
brand/accent | #F59E0B (amber-500) | #FBBF24 (amber-400) | Amber brighter |
BORDER TOKENSโ
| Token name | Light | Dark | Notes |
|---|---|---|---|
border/default | #E4E4E7 (zinc-200) | #27272A (zinc-800) | Invisible hairline |
border/light | #F4F4F5 (zinc-100) | #18181B (zinc-900) | Subtle |
border/strong | #D4D4D8 (zinc-300) | #3F3F46 (zinc-700) | Visible accent |
border/focus | #059669 | #10B981 | Focus ring |
border/error | #EF4444 | #F87171 (red-400) | Brighter di dark |
STATUS TOKENS (Attendance)โ
| Token | Light FG | Light BG | Dark FG | Dark BG |
|---|---|---|---|---|
status/hadir | #047857 (emerald-700) | #ECFDF5 (emerald-50) | #34D399 (emerald-400) | rgba(16,185,129,0.15) |
status/izin | #B45309 (amber-700) | #FFFBEB (amber-50) | #FBBF24 (amber-400) | rgba(245,158,11,0.15) |
status/sakit | #1D4ED8 (blue-700) | #EFF6FF (blue-50) | #60A5FA (blue-400) | rgba(59,130,246,0.15) |
status/alpha | #B91C1C (red-700) | #FEF2F2 (red-50) | #F87171 (red-400) | rgba(239,68,68,0.15) |
status/terlambat | #C2410C (orange-700) | #FFF7ED (orange-50) | #FB923C (orange-400) | rgba(249,115,22,0.15) |
STATUS TOKENS (Payment)โ
| Token | Light FG | Light BG | Dark FG | Dark BG |
|---|---|---|---|---|
status/lunas | #047857 | #ECFDF5 | #34D399 | rgba(16,185,129,0.15) |
status/belum-bayar | #B45309 | #FFFBEB | #FBBF24 | rgba(245,158,11,0.15) |
status/overdue | #B91C1C | #FEF2F2 | #F87171 | rgba(239,68,68,0.15) |
FEEDBACK TOKENSโ
| Token | Light FG | Light BG | Dark FG | Dark BG |
|---|---|---|---|---|
feedback/success | #047857 | #ECFDF5 | #34D399 | rgba(16,185,129,0.15) |
feedback/warning | #B45309 | #FFFBEB | #FBBF24 | rgba(245,158,11,0.15) |
feedback/error | #B91C1C | #FEF2F2 | #F87171 | rgba(239,68,68,0.15) |
feedback/info | #1D4ED8 | #EFF6FF | #60A5FA | rgba(59,130,246,0.15) |
๐จ GRADIENT DARK MODE MAPPINGโ
Hero Card Gradient (Dashboard)โ
Light mode:
linear-gradient(156deg,
#057E6B 0%,
#065F53 42%,
#06443E 100%
)
Dark mode:
linear-gradient(156deg,
#064E3B 0%, /* emerald-900 */
#022C22 42%, /* emerald-950 */
#0A0C0A 100% /* near-black with green tint */
)
Alternatif dark gradient (jika mau lebih dramatic):
linear-gradient(156deg,
#065F46 0%,
rgba(0,0,0,0.4) 100%
), #18181B
Gradient Card Successโ
Light:
linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%)
Dark:
linear-gradient(135deg,
rgba(16,185,129,0.1) 0%,
rgba(5,150,105,0.05) 100%
), #18181B
Gradient Card Warningโ
Light:
linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%)
Dark:
linear-gradient(135deg,
rgba(245,158,11,0.1) 0%,
rgba(217,119,6,0.05) 100%
), #18181B
๐ SHADOW ADAPTATIONโ
Rule: Shadows less visible in dark modeโ
Di light mode, shadow menciptakan elevation. Di dark mode, elevation muncul dari surface yang lebih terang (perceptual hierarchy), bukan shadow.
Shadow tokens (Dark mode):โ
| Token | Light | Dark |
|---|---|---|
shadow/sm | 0 1px 3px rgba(0,0,0,0.1) | 0 1px 3px rgba(0,0,0,0.3) |
shadow/md | 0 4px 6px rgba(0,0,0,0.07) | 0 4px 6px rgba(0,0,0,0.25) |
shadow/lg | 0 10px 15px rgba(0,0,0,0.1) | 0 10px 15px rgba(0,0,0,0.4) |
shadow/card | 0 2px 6px rgba(0,0,0,0.04) | none (pakai border accent) |
shadow/hero | 0 8px 24px rgba(5,150,105,0.25) | 0 8px 24px rgba(0,0,0,0.5) |
shadow/fab | 0 8px 20px rgba(5,150,105,0.35) | 0 8px 20px rgba(16,185,129,0.4) |
Untuk card di dark mode, gunakan border accent sebagai ganti shadow:โ
/* Light mode */
.card {
background: #FFFFFF;
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
border: none;
}
/* Dark mode */
.card {
background: #18181B;
box-shadow: none;
border: 1px solid #27272A; /* subtle accent */
}
๐ผ๏ธ ILLUSTRATION & IMAGERY DARK MODEโ
Illustrations (empty states):โ
Option A โ Multi-variant approach: Buat 2 version illustration per empty state:
- Light variant: original colors
- Dark variant: desaturated + brightened
Option B โ Tint overlay: Single illustration + CSS filter:
/* Dark mode */
.empty-state-illustration {
filter: brightness(0.9) saturate(0.7);
}
Recommended: Option A untuk Figma production file. Beri designer task buat variants di component.
Icons:โ
Icons pakai color token reference โ auto-adapt ke dark mode. Tidak perlu dark variant separate.
Photos:โ
Raw photos (foto santri, foto kegiatan) tidak perlu dimodifikasi di dark mode. Tambahkan subtle overlay untuk cohesion:
.photo-wrapper::after {
background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.3));
}
โ DARK MODE IMPLEMENTATION CHECKLIST (PER SCREEN)โ
Untuk setiap screen, verify:
Structuralโ
- Background pakai
surface/background(bukan hardcode white/black) - Cards pakai
surface/card - AppBar pakai
surface/appbar - BottomNav pakai
surface/bottomtab
Textโ
- Headings pakai
text/primary - Body text pakai
text/primary - Subtext pakai
text/secondary - Meta pakai
text/tertiary(consistent across modes) - Links pakai
text/link
Borders & Dividersโ
- Card borders pakai
border/default - Dividers pakai
border/light
Interactiveโ
- Buttons primary pakai
brand/primary - Focus indicators visible di dark
- Hover states readable di dark
- Active tab indicator tetap terlihat
Status & Feedbackโ
- Status chips pakai dark variants (alpha transparency)
- Feedback alerts (success/error/warning) adjusted
- Badges readable di dark
Shadows & Elevationโ
- Shadow intensity naik (lebih opaque)
- Card pakai border di dark (alternatif shadow)
- Hierarchy masih clear via brightness
Imagesโ
- Illustrations punya dark variant
- Photos ditambah subtle overlay
- Icons pakai token color (auto-adapt)
Contrast QAโ
- Run Stark plugin โ 0 violations
- Test di brightness 30% โ masih readable
- Test di brightness 100% โ tidak menyilaukan
๐ฏ COMMON DARK MODE BUGS TO WATCHโ
Bug 1: "Flash of white" saat switch modeโ
Cause: Layout element masih pakai hardcoded white.
Fix: Search #FFFFFF / white di Figma โ replace dengan surface/card token.
Bug 2: Modal overlay invisibleโ
Cause: Overlay rgba(0,0,0,0.5) tidak contrast dengan dark background.
Fix: Naikkan alpha ke 0.7 untuk dark mode.
Bug 3: "Low contrast everywhere"โ
Cause: Lupa adjust brand colors. Emerald-600 di light (OK) dipertahankan di dark (too dark). Fix: Shift brand ke emerald-500 untuk interactive element.
Bug 4: Button di hover jadi hilangโ
Cause: Hover state darken di light, tapi dark mode sudah gelap โ tambah dark = invisible. Fix: Inverse direction di dark. Hover = lighten (brighter emerald-400).
Bug 5: Form field invisibleโ
Cause: Border border/default (zinc-800) hampir identik dengan BG (zinc-900).
Fix: Pakai border/strong untuk form fields di dark, atau tambah subtle BG zinc-800 untuk field itu sendiri.
Bug 6: Chart / data viz hilangโ
Cause: Chart colors optimized untuk white BG. Fix: Buat palette terpisah untuk chart di dark mode (lebih saturated).
๐ MODE SWITCHING BEHAVIORโ
Ketika user toggle light โ dark:
Smooth transitionโ
CSS approach (Flutter akan beda):
* {
transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease;
}
Hindari transition untuk:
transform(jadi weird)opacitydi hero (mengganggu)position(layout shift)
Auto mode detectionโ
Default behavior:
- Check system preference (
prefers-color-schemefor web, system setting for Flutter) - Override dengan user preference (tersimpan di SharedPreferences/localStorage)
- Pilihan:
light,dark,system(auto)
Status bar adaptationโ
Dark mode aktif:
- Android:
SystemUiOverlayStyle.light(icons white) - iOS:
UIStatusBarStyle.lightContent
Light mode:
- Android:
SystemUiOverlayStyle.dark(icons dark) - iOS:
UIStatusBarStyle.darkContent
Per-screen override:
- Hero gradient green (dark): always light status bar
- Empty state white (light): always dark status bar
๐งช TESTING DARK MODEโ
Manual test list:โ
- Test mode toggle dari Profil โ Settings โ Theme
- Test system auto-switch (set device ke dark di system settings)
- Test semua 42 screen di dark
- Test smooth transition (no jank)
- Test dengan contrast simulator (kurangi kontras display 20%)
- Test dengan night filter (blue light reduction on)
- Test screenshot print mode (contrast masih OK untuk PDF)
Automated test (Flutter):โ
testGoldens('AmalButton renders in dark mode', (tester) async {
await tester.pumpWidgetBuilder(
AmalButton(label: 'Test'),
wrapper: materialAppWrapper(theme: AmalTheme.dark),
);
await screenMatchesGolden(tester, 'button_dark');
});
Setup golden tests per component dengan light + dark variants.
๐ SPECIAL CASESโ
Hero Card Dark Variantโ
Hero green gradient butuh treatment khusus di dark. Sisi tidak boleh jadi flat black (kehilangan identity), tapi juga jangan se-saturated light mode.
Recommended dark hero:
.hero-card-dark {
background: linear-gradient(156deg,
#064E3B 0%, /* emerald-900 */
rgba(6, 78, 59, 0.7) 50%,
#0C1411 100% /* off-black with green undertone */
);
box-shadow: 0 8px 24px rgba(5, 150, 105, 0.15);
border: 1px solid rgba(16, 185, 129, 0.2); /* subtle emerald accent */
}
/* Text tetap white, opacity naikkan sedikit */
.hero-card-dark .greeting-label {
color: rgba(255, 255, 255, 0.75); /* naik dari 0.6 di light */
}
.hero-card-dark .subtitle {
color: rgba(255, 255, 255, 0.92); /* naik dari 0.75 */
}
Glassmorphism di Darkโ
Child switcher glass di hero card โ adjust untuk dark:
/* Light */
.child-switcher-glass {
background: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.3);
}
/* Dark */
.child-switcher-glass-dark {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px); /* stronger blur di dark */
}
Selection/Highlightโ
Text selection color di dark mode:
::selection {
background: rgba(16, 185, 129, 0.3);
color: #FAFAFA;
}
Scrollbar (jika visible di Flutter / web)โ
/* Dark mode scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #18181B;
}
::-webkit-scrollbar-thumb {
background: #3F3F46;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #52525B;
}
๐ BONUS: AMOLED Modeโ
Untuk device AMOLED (OLED), pure black (#000000) menghemat battery karena pixel benar-benar mati. Opsional tambah mode "AMOLED":
amoled theme:
background: #000000
card: #0A0A0A
variant: #141414
... (rest mirip dark, tapi lebih dalam)
Status: Nice-to-have, skip untuk v1.0. Tambah di Phase 2 kalau ada permintaan.
Status: Pairing complete, ready untuk dark mode expansion Last updated: 2026-04-16 Version: 1.0