Розробка темної теми (Dark Mode) мобільного застосунку
Dark mode — це не «зробити всё темним». Це паралельна кольорова система, яка повинна забезпечувати ті ж контрастні відношення, ієрархію та читаємість, що й світла тема. Якщо просто інвертувати кольори — отримаєте щось візуально некоректне: тені стануть світлішими за фон, акцентний колір втратить насиченість, зображення виривалися будуть з контексту.
Як не надо: інверсія та hardcoded кольори
Перша та найдорожча помилка — використовувати захардкодені кольори замість семантичних токенів. Color.white, #FFFFFF, UIColor(red:1 green:1 blue:1 alpha:1) — все це ломається при переключенні теми. Виправляти це потім у готовому застосунку — переписувати всі UI-компоненти.
Правильний підхід — семантичні токени: background.primary, text.secondary, surface.elevated, accent.default. На iOS це UIColor.systemBackground, UIColor.label, UIColor.secondaryLabel та кастомні кольори через Asset Catalog з Light та Dark варіантами. На Android — Material Design 3 з colorScheme через MaterialTheme.colorScheme.surface, onSurface, surfaceVariant.
У Flutter: ThemeData.light() та ThemeData.dark() з повним ColorScheme, переключення через MaterialApp(themeMode: ...). У React Native: useColorScheme() hook з ядра + Appearance.getColorScheme() для ініціалізації.
Правила для темної палітри
Темна тема — не просто темний фон. Кілька правил, які нарушають частіше всього:
Elevation через освітлення, не тені. У Material Design 3 поверхні на різних рівнях z-index у темній темі розрізняються яскравістю: чим вище, тим світліше. surface → surfaceContainer → surfaceContainerHigh. Тені в темній темі майже невидимі — вони замінюються тональним розділенням.
Контраст тексту. WCAG AA вимагає мінімум 4.5:1 для звичайного тексту. Білий #FFFFFF на темному #121212 = 18.1:1 — занадто висок, утомлює очі. Оптимально #E0E0E0 на #121212 = 14.7:1. Google рекомендує #FFFFFF з 87% opacity для primary text.
Акцентний колір. Багато акцентних кольорів у темній темі потрібно трохи десатурувати та освітлити. Яскраво-синій #2196F3 на темному фоні вібрує та викликає дискомфорт. #90CAF9 — правильна версія для dark mode.
Зображення та ілюстрації. Фото не змінюються. Ілюстрації з білим фоном — проблема. Рішення: SVG-ілюстрації з прозорим фоном та адаптивними кольорами через currentColor.
Динамічне переключення
iOS з iOS 13 підтримує traitCollectionDidChange — система автоматично сповіщає про зміну теми. SwiftUI перерисовує з @Environment(\.colorScheme). UIKit потребує явного override func traitCollectionDidChange.
Android: AppCompatDelegate.setDefaultNightMode() для програмного переключення. DayNight тема в styles.xml. Активність перезапускається при смені теми — потрібно зберігати стан через ViewModel або onSaveInstanceState.
Важливий edge case: користувач змінює тему в системних налаштуваннях, поки застосунок відкритий на фоні. При виході на передний план застосунок повинен застосувати нову тему без помітного мигання. На Android це recreate() активності або configChanges: uiMode у маніфесті з ручною обробкою.
Тестування темної теми
Три рівні:
- Figma — всі компоненти з light/dark варіантами через Figma Variables
- Симулятор/еумулятор — переключення через швидкі налаштування
- Фізичне пристрій в OLED-режимі (iPhone 12+, Samsung Galaxy) — перевіряємо «чистоту» чорного, відсутність halo-ефекту навколо світлих елементів
Інструменти: Xcode Accessibility Inspector для перевірки контрасту, Android Accessibility Scanner. Перевіряємо всі екрани, всі модальні вікна, всі алерти — вони часто використовують системні кольори та ломаються в першу чергу.
Процес та строки
Аудит існуючої кодової бази (знайти всі hardcoded кольори) → рефакторинг на токени → створення dark palette у дизайн-системі → реалізація → тестування.
| Застосунок | Строк |
|---|---|
| Новий, токени з нуля | 2–3 дні |
| Готовий, потрібен рефакторинг кольорів | 3–5 днів |
| Складний, багато кастомних компонентів | 5–7 днів |
Вартість розраховується індивідуально після аудиту проекту.







