Тёмная тема без боли: поверхности, контраст, акценты

Тёмная тема кажется простой задачей: поменять фон на тёмный, текст — на светлый. На деле она требует пересобрать систему поверхностей, акцентов и состояний. Разберём, что меняется при переходе в тёмный режим и как сделать его частью дизайн-системы, а не отдельным набором исключений.

Редакция Флагштока9 октября 20268 мин чтения
Тёмная тема без боли: поверхности, контраст, акценты

Тёмная тема — не инверсия

Простая инверсия цветов ломает почти всё: тени превращаются в светлые ореолы, фирменные цвета становятся кислотными, фотографии выглядят как негативы, а иерархия поверхностей переворачивается. Тёмная тема — это отдельная палитра, построенная по тем же принципам, что и светлая, но с учётом другого восприятия.

В светлом интерфейсе глубину создают тени: карточка «поднимается» над фоном, потому что отбрасывает тень. На тёмном фоне тени почти не видны, и этот язык перестаёт работать. Нужен другой способ показать, что лежит выше, а что ниже.

Кроме того, человеческий глаз по-разному воспринимает светлое на тёмном и тёмное на светлом. Яркие насыщенные цвета на тёмном фоне кажутся крупнее и «вибрируют», тонкие светлые линии расплываются. Поэтому акценты, толщины шрифтов и границ приходится пересматривать.

Поэтому начинать стоит не с макетов, а с токенов. Если светлая тема уже построена на семантических переменных, тёмная сводится к новому набору значений для тех же ролей. Если же цвета зашиты в компоненты, первый шаг — вынести их в семантику, иначе тёмная тема превратится в сотни точечных исключений.

Тёмная тема — это вторая палитра с той же логикой, а не перевёрнутая первая.

Поверхности и глубина

Начните с базового фона. Чистый чёрный #000000 даёт максимальный контраст со светлым текстом, но делает интерфейс жёстким и плохо передаёт глубину: темнее фона быть нечему. Material Design, например, рекомендует в качестве базовой поверхности тёмно-серый #121212. Чистый чёрный оправдан на OLED-экранах в отдельных режимах, где важно энергопотребление, но не как универсальное решение.

Глубина в тёмной теме передаётся светлотой: чем выше поверхность, тем она светлее. Фон страницы — самый тёмный, карточки — чуть светлее, всплывающие меню и модальные окна — ещё светлее. Разница между уровнями небольшая, но заметная. Material Design реализует это через полупрозрачный белый слой поверх базовой поверхности, прозрачность которого растёт с уровнем возвышения.

Ограничьте число уровней. Обычно хватает четырёх-пяти: фон, основная поверхность, приподнятая поверхность, оверлей, иногда — утопленная область для полей ввода. Каждый уровень — отдельный семантический токен: surface.base, surface.raised, surface.overlay.

Границы помогают там, где светлоты недостаточно. Тонкая полупрозрачная светлая граница отделяет карточку от фона лучше, чем тень. Но помните о критерии 1.4.11: если граница — единственный признак интерактивного элемента, например поля ввода, ей нужен контраст 3:1.

  • фон — самый тёмный уровень
  • выше — значит светлее
  • 4–5 уровней поверхностей
  • границы вместо теней там, где нужно
Иллюстрация к разделу «Поверхности и глубина»

Текст и контраст

Требования WCAG к контрасту одинаковы для любой темы: 4,5:1 для обычного текста и 3:1 для крупного. На тёмном фоне выполнить их обычно проще, поэтому возникает обратная проблема — избыточный контраст. Ярко-белый текст на почти чёрном фоне у части читателей создаёт ощущение ореола вокруг букв, особенно на длинных текстах.

Распространённый приём — использовать для текста не чистый белый, а белый с прозрачностью или светло-серый. В Material Design для текста на тёмных поверхностях рекомендуются уровни непрозрачности 87% для основного текста, 60% для второстепенного и 38% для неактивного. Важно проверить, что второстепенный текст на самой светлой поверхности всё ещё проходит 4,5:1.

Светлый текст на тёмном фоне визуально выглядит жирнее. Если в светлой теме основной текст набран начертанием 400, в тёмной иногда стоит проверить, не лучше ли он читается чуть легче, а мелкие подписи — с немного увеличенным межбуквенным интервалом. Это тонкая настройка, и решать её нужно на реальных экранах, а не по макету.

Проверяйте контраст на всех уровнях поверхностей. Пара, которая работает на фоне страницы, может не пройти на модальном окне, где фон светлее.

На тёмном фоне контраст легко получить — сложнее не переборщить. Цель — 4,5:1 и выше, но без чистого белого на чистом чёрном.

Акценты и цвета состояний

Насыщенный фирменный цвет, хорошо работающий на белом, на тёмном фоне начинает рябить и может не пройти по контрасту. Решение — использовать более светлые и менее насыщенные тона той же палитры. В шкале от 50 до 900 в светлой теме часто используют тона 500–700, а в тёмной — 200–300.

Это касается и семантических цветов: ошибки, предупреждения, успеха. Красный, который в светлой теме подходит для текста ошибки, на тёмном фоне обычно слишком тёмный. Сделайте отдельные значения для каждой темы в семантических токенах — color.text.danger, color.border.success — и проверьте контраст каждого.

Используйте акценты экономно. На тёмном фоне любое яркое пятно притягивает взгляд сильнее, чем на светлом, поэтому три цветные кнопки на экране воспринимаются как шум. Оставьте акцентный цвет для главного действия и важных статусов.

Отдельно проверьте фокус. Индикатор, рассчитанный на светлую тему, может потеряться на тёмной. Контур фокуса должен иметь контраст не ниже 3:1 с соседними цветами в обеих темах — проще всего завести для него собственный токен.

Изображения, иллюстрации и графики

Фотографии обычно не требуют изменений, но очень яркие снимки на тёмном фоне могут слепить. Иногда помогает лёгкое снижение яркости через CSS-фильтр, хотя применять его ко всем изображениям без разбора не стоит: страдает цветопередача.

С иллюстрациями и логотипами сложнее. PNG с прозрачным фоном и тёмными линиями на тёмном фоне просто исчезнет. Нужны отдельные версии для тёмной темы, которые подключаются через элемент picture с медиазапросом prefers-color-scheme, или SVG, использующие currentColor и CSS-переменные.

Графики и диаграммы требуют отдельной палитры. Линии сетки должны быть едва заметными, а цвета серий — различимыми между собой и с фоном. Проверьте, что серии различаются не только цветом, но и формой маркеров или подписями: это требование критерия 1.4.1 действует в обеих темах.

Не забывайте про тени в иллюстрациях и мокапах устройств: тёмная тень на тёмном фоне не видна, а светлая выглядит неестественно. Часто лучше заменить тень мягкой светлой обводкой или подложкой чуть светлее фона.

Техническая реализация

Стройте тему на семантических CSS-переменных. Базовые значения объявляются на :root, а тёмная тема переопределяет их в медиазапросе prefers-color-scheme: dark или в селекторе вроде [data-theme="dark"]. Компоненты используют только семантические переменные и не знают, какая тема активна.

Добавьте свойство color-scheme: light dark. Оно сообщает браузеру, что страница поддерживает обе темы, и браузер перекрашивает полосы прокрутки, стандартные поля форм и фон по умолчанию. Мета-тег theme-color с медиаусловием позволяет окрасить интерфейс мобильного браузера под текущую тему.

Дайте пользователю выбор из трёх вариантов: светлая, тёмная и системная. Выбор сохраняйте, например, в localStorage, а тему применяйте небольшим скриптом в head до отрисовки страницы. Иначе при загрузке появится вспышка светлой темы — особенно неприятная вечером.

Тестируйте обе темы одинаково тщательно. Скриншотные тесты в двух режимах, проверка контраста для каждой пары токенов и ручной проход по ключевым экранам ловят большинство проблем до релиза.

  • семантические переменные вместо прямых цветов
  • color-scheme для системных элементов
  • три режима: светлый, тёмный, системный
  • применение темы до первой отрисовки

Частые вопросы

Нужна ли тёмная тема каждому продукту?

Не обязательно, но многие пользователи включают тёмный режим на уровне системы и ожидают, что продукты его поддержат. Для приложений, которыми пользуются долго или вечером, тёмная тема особенно востребована.

Можно ли использовать чистый чёрный фон?

Можно, но это усложняет передачу глубины и усиливает контраст с белым текстом. Тёмно-серый фон вроде #121212 даёт пространство для более светлых уровней поверхностей и мягче для чтения.

Как избежать вспышки светлой темы при загрузке?

Определяйте тему небольшим синхронным скриптом в head, который читает сохранённый выбор или системную настройку и выставляет атрибут на html до отрисовки. Свойство color-scheme дополнительно задаёт правильный фон по умолчанию.

Редакция Флагштока

Пишем об UX/UI-дизайне: исследования, интерфейсы, дизайн-системы и доступность.