Lewati ke konten utama

๐ŸŒ™ 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 nameLightDarkNotes
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/overlayrgba(0,0,0,0.5)rgba(0,0,0,0.7)Deeper dim di dark
surface/appbar#FFFFFF (solid)#09090B + blurDark: glass effect
surface/bottomtab#FFFFFF#18181BBottom nav
surface/bottomtab-activergba(5,150,105,0.08)rgba(16,185,129,0.15)Active tab tint

TEXT TOKENSโ€‹

Token nameLightDarkNotes
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#18181BUntuk text di atas brand solid
text/link#059669 (emerald-600)#34D399 (emerald-400)Links brighter di dark
text/on-primary#FFFFFF#FFFFFFSelalu white pada brand

BRAND TOKENSโ€‹

Token nameLightDarkNotes
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 nameLightDarkNotes
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#10B981Focus ring
border/error#EF4444#F87171 (red-400)Brighter di dark

STATUS TOKENS (Attendance)โ€‹

TokenLight FGLight BGDark FGDark 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)โ€‹

TokenLight FGLight BGDark FGDark BG
status/lunas#047857#ECFDF5#34D399rgba(16,185,129,0.15)
status/belum-bayar#B45309#FFFBEB#FBBF24rgba(245,158,11,0.15)
status/overdue#B91C1C#FEF2F2#F87171rgba(239,68,68,0.15)

FEEDBACK TOKENSโ€‹

TokenLight FGLight BGDark FGDark BG
feedback/success#047857#ECFDF5#34D399rgba(16,185,129,0.15)
feedback/warning#B45309#FFFBEB#FBBF24rgba(245,158,11,0.15)
feedback/error#B91C1C#FEF2F2#F87171rgba(239,68,68,0.15)
feedback/info#1D4ED8#EFF6FF#60A5FArgba(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):โ€‹

TokenLightDark
shadow/sm0 1px 3px rgba(0,0,0,0.1)0 1px 3px rgba(0,0,0,0.3)
shadow/md0 4px 6px rgba(0,0,0,0.07)0 4px 6px rgba(0,0,0,0.25)
shadow/lg0 10px 15px rgba(0,0,0,0.1)0 10px 15px rgba(0,0,0,0.4)
shadow/card0 2px 6px rgba(0,0,0,0.04)none (pakai border accent)
shadow/hero0 8px 24px rgba(5,150,105,0.25)0 8px 24px rgba(0,0,0,0.5)
shadow/fab0 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)
  • opacity di hero (mengganggu)
  • position (layout shift)

Auto mode detectionโ€‹

Default behavior:

  1. Check system preference (prefers-color-scheme for web, system setting for Flutter)
  2. Override dengan user preference (tersimpan di SharedPreferences/localStorage)
  3. 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:โ€‹

  1. Test mode toggle dari Profil โ†’ Settings โ†’ Theme
  2. Test system auto-switch (set device ke dark di system settings)
  3. Test semua 42 screen di dark
  4. Test smooth transition (no jank)
  5. Test dengan contrast simulator (kurangi kontras display 20%)
  6. Test dengan night filter (blue light reduction on)
  7. 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