--- name: dark-mode description: Web and App implementation guide for Dark Mode Design. Trigger when user wants dark surfaces, reduced eye strain, and premium sleek aesthetics. date_added: "2026-06-17" risk: safe source: self source_type: self --- # Dark Mode Design > "Not just inverted colors. A carefully constructed hierarchy of light on dark." ## When to Use Use this sub-style when the user's request matches the aesthetic described above. This is a child reference of the `design-it` skill and is not meant to be triggered directly. ## Core Principles 1. **Never Pure Black**: True `#000000` causes smearing on OLED screens and extreme eye strain with white text. Use dark greys (e.g., `#121212` or `#0A0A0A`). 2. **Elevation via Lightness**: In light mode, shadows show elevation. In dark mode, shadows are invisible, so elevated surfaces must be lighter than the background. 3. **Desaturated Accents**: Saturated colors vibrate painfully against dark backgrounds. Tone down the saturation of brand colors. ## Visual DNA - **Colors**: **Midnight Luxury** or **Minimalist Slate** (inverted). Background `#121212`. Elevated cards `#1E1E1E`, `#252525`. Primary text `#E1E1E1` (not `#FFFFFF`). - **Typography**: Standard highly readable sans-serifs, but often dropped down one font weight compared to light mode, as light text on dark backgrounds appears optically thicker. - **Shadows**: Pure black shadows, but with much lower opacity, mostly to separate slightly different shades of grey. ## Web Implementation - **CSS Example**: ```css :root { --bg-base: #121212; --bg-elevated-1: #1E1E1E; --bg-elevated-2: #242424; --text-high-emphasis: rgba(255, 255, 255, 0.87); --text-medium-emphasis: rgba(255, 255, 255, 0.60); /* Accent color: Desaturated purple instead of bright purple */ --accent-color: #BB86FC; } body { background-color: var(--bg-base); color: var(--text-high-emphasis); font-weight: 300; /* Thinner weight for dark mode */ } .dark-card { background-color: var(--bg-elevated-1); border-radius: 8px; padding: 24px; /* Very subtle border can help separate dark surfaces */ border: 1px solid rgba(255, 255, 255, 0.05); } .dark-card:hover { /* On hover, the element moves closer to the user, so it gets lighter */ background-color: var(--bg-elevated-2); } .dark-btn { background-color: var(--accent-color); color: #000; /* Dark text on light accent is highly readable */ font-weight: 600; border: none; padding: 12px 24px; border-radius: 4px; } ``` ## App Implementation ### SwiftUI ```swift struct DarkModeView: View { // Force Dark Mode on this specific view (or use system settings) @Environment(\.colorScheme) var colorScheme var body: some View { ScrollView { VStack(spacing: 20) { // Primary elevated card VStack(alignment: .leading, spacing: 12) { Text("Elevation via Lightness") .font(.headline) .foregroundColor(.primary) // Auto-adapts Text("In dark mode, elevated surfaces are lighter grey, not shadowed.") .font(.subheadline) .foregroundColor(.secondary) // Auto-adapts } .padding() .frame(maxWidth: .infinity, alignment: .leading) // Use native semantic colors. .secondarySystemBackground is lighter than .systemBackground .background(Color(UIColor.secondarySystemBackground)) .cornerRadius(12) // Desaturated Accent Button Button(action: {}) { Text("Desaturated Accent") .fontWeight(.semibold) .foregroundColor(.black) // Dark text on light accent .padding() .frame(maxWidth: .infinity) .background(Color(red: 0.73, green: 0.52, blue: 0.98)) // #BB86FC (Desaturated purple) .cornerRadius(8) } } .padding() } // #121212 is the standard dark mode background, which systemBackground maps to closely .background(Color(UIColor.systemBackground)) } } // .preferredColorScheme(.dark) to force ``` - **Rely on Semantics**: SwiftUI's `Color.primary`, `Color.secondary`, `Color(UIColor.systemBackground)` and `Color(UIColor.secondarySystemBackground)` handle perfect dark mode transitions automatically. - Avoid forcing explicit hex codes for backgrounds unless you are building a custom-themed app. ### Flutter ```dart import 'package:flutter/material.dart'; class DarkModeApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // Configure the Dark Theme themeMode: ThemeMode.dark, darkTheme: ThemeData.dark().copyWith( scaffoldBackgroundColor: const Color(0xFF121212), // Standard dark background cardColor: const Color(0xFF1E1E1E), // Elevated surface colorScheme: const ColorScheme.dark().copyWith( primary: const Color(0xFFBB86FC), // Desaturated accent onPrimary: Colors.black, // Dark text on light accent surface: const Color(0xFF1E1E1E), ), ), home: Scaffold( appBar: AppBar(title: const Text('Dark Mode', style: TextStyle(color: Colors.white70))), body: ListView( padding: const EdgeInsets.all(16), children: [ Card( elevation: 0, // Shadows don't show well anyway shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: Colors.white.withOpacity(0.05)), // Subtle border ), child: const Padding( padding: EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Elevation via Lightness', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), SizedBox(height: 8), Text('Elevated surfaces use lighter greys.', style: TextStyle(color: Colors.white60)), ], ), ), ), const SizedBox(height: 20), ElevatedButton( onPressed: () {}, child: const Text('Desaturated Accent'), ), ], ), ), ); } } ``` - When using `ThemeData.dark()`, actively override `scaffoldBackgroundColor` to `#121212` and `cardColor` to `#1E1E1E`. - Ensure your `colorScheme.onPrimary` is black so text is readable when placed on top of your desaturated primary accent button. ### React Native ```jsx import { useColorScheme } from 'react-native'; const DarkModeScreen = () => { const isDark = useColorScheme() === 'dark'; // Custom dark theme dictionary const theme = { bgBase: isDark ? '#121212' : '#FFFFFF', bgElevated: isDark ? '#1E1E1E' : '#F5F5F5', textHigh: isDark ? 'rgba(255, 255, 255, 0.87)' : 'rgba(0, 0, 0, 0.87)', textMedium: isDark ? 'rgba(255, 255, 255, 0.60)' : 'rgba(0, 0, 0, 0.60)', accent: isDark ? '#BB86FC' : '#6200EE', // Desaturated for dark, vibrant for light onAccent: isDark ? '#000' : '#FFF', }; return ( Elevation via Lightness Elevated surfaces use lighter greys. Desaturated Accent ); }; ``` - Rely on `useColorScheme()` hook from React Native. - Define a strict dictionary of your color tokens. Notice how `theme.accent` shifts from a vibrant purple (`#6200EE`) in light mode to a desaturated pastel purple (`#BB86FC`) in dark mode. ### Jetpack Compose ```kotlin @Composable fun DarkModeScreen() { // Typically this logic lives in your Theme.kt val darkColors = darkColorScheme( background = Color(0xFF121212), surface = Color(0xFF1E1E1E), primary = Color(0xFFBB86FC), onPrimary = Color.Black, onBackground = Color.White.copy(alpha = 0.87f), onSurface = Color.White.copy(alpha = 0.87f) ) MaterialTheme(colorScheme = darkColors) { Column( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .padding(16.dp) ) { Card( colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(12.dp), border = BorderStroke(1.dp, Color.White.copy(alpha = 0.05f)) ) { Column(modifier = Modifier.padding(24.dp)) { Text("Elevation via Lightness", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface) Spacer(Modifier.height(8.dp)) Text("Elevated surfaces use lighter greys.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) } } Spacer(Modifier.height(20.dp)) Button( onClick = {}, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ), modifier = Modifier.fillMaxWidth().height(50.dp) ) { Text("Desaturated Accent", fontWeight = FontWeight.Bold) } } } } ``` - Material 3 handles Dark Mode semantics perfectly. Define your `darkColorScheme`. - Use `Color(0xFF1E1E1E)` for `surface` and `Color(0xFF121212)` for `background`. Compose will automatically map these to `Card`s and Scaffolds. ## Do's and Don'ts - **DO**: Meet WCAG contrast standards. Just because it's dark doesn't mean the text should be illegibly dim. - **DON'T**: Use bright, highly saturated primary colors. ## Limitations - This is a styling reference and does not replace environment-specific validation, accessibility testing, or expert review. - Ensure appropriate contrast ratios and responsive behaviors are verified separately.