Переключатель тёмной темы — это CSS-селектор, а не JS:
styles/theme/common/var-dark.scss
[data-theme='dark'],
.dark {
--lh-bg-color: #2A2520;
--lh-text-primary: #E8E0D8;
// ... 118 переопределений
}
Два способа переключения:
<!-- Способ 1: data-атрибут (рекомендуется, семантика прозрачна) -->
<htmldata-theme="dark">
<!-- Способ 2: class (для совместимости со старым кодом / сторонними библиотеками переключения) -->
<bodyclass="dark">
Слою UI (ConfigProvider в библиотеках или бизнес-коду) достаточно переключить этот атрибут / класс — и все компоненты мгновенно меняют цвет: CSS-переменные вычисляются в runtime, без затрат на перерендер.
Тёмный файл содержит 118 записей, а не полный набор из 185. Переопределяются только те токены, которые нужно перевернуть; остальные наследуют светлые значения.
Принцип дизайна — перенаправление семантических токенов, а не переписывание каждого компонента:
// ✅ Хорошо: в тёмной теме указываем на тёмные семантические токены
[data-theme='dark'],
.dark {
--lh-menu-item-color: var(--lh-text-primary);
--lh-table-cell-color: var(--lh-text-primary);
}
// ❌ Плохо: в тёмной теме записано конкретное значение цвета
[data-theme='dark'],
.dark {
--lh-menu-item-color: rgba(255, 255, 255, 0.85); // теряет связь с text-primary; при последующей настройке палитры менять придётся в двух местах
Токены в var.scss, значение которых имеет вид #{$lh-foo}:
var.scss
:root {
--lh-menu-item-color: #{$lh-menu-item-color}; // SCSS-переменная на этапе компиляции заменяется конкретным светлым значением
}
SCSS-переменная запекается на этапе компиляции в литеральное значение, что эквивалентно:
:root {
--lh-menu-item-color: rgba(0, 0, 0, 0.88); // В тёмной теме автоматически не перевернётся!
}
Результат: в тёмной теме текст меню / таблиц / таймлайна использует светлые значения — чёрный текст на чёрном фоне, невидим. Такие пробелы действительно случались; записи об исправлениях — в changeset-е 0.4.0.
Fallback у var()вычисляется в runtime: если хотя бы один токен в цепочке имеет тёмное переопределение, вся цепочка переворачивается вместе с ним. Дублировать объявление не нужно.
Сравнительная таблица:
Запись
Момент вычисления
Поведение в тёмной теме
var(--lh-foo, fallback)
runtime
✅ Переворачивается, если у токена в цепочке есть тёмное переопределение
#{$lh-foo} (SCSS-интерполяция)
этап компиляции
❌ Запекается в светлый цвет; требуется явное перенаправление в var-dark
SCSS-функции mix($lh-a, $lh-b) и др.
этап компиляции
❌ То же, что и выше
Осветление тёмной темы у пресетов с тёмным основным цветом
Из пяти встроенных пресетов те, что с тёмным основным цветом (青花瓷 (сине-белый фарфор) / 朱砂 (киноварь) / 水墨 (тушь)), в тёмной теме «тонут» в цвете фона страницы. Это автоматически обрабатывает buildPresetDarkVars:
Правило определения: контрастность --lh-primary-color относительно тёмного фона страницы #1A1A2E (DARK_PAGE_BG) меньше 3 (DARK_PRIMARY_CONTRAST_THRESHOLD) — цвет считается тёмным основным, и тогда:
[data-theme='dark'],
.dark {
--lh-primary-color: color-mix(insrgb, <литеральноезначение>55%, #fff); /* Осветление на 55% */
Почему для осветления основного цвета используется литеральное значение, а не var(): color-mix(in srgb, var(--lh-primary-color) 55%, #fff) создаёт цикл CSS-самоссылки (значение --lh-primary-color ссылается само на себя). Поэтому в эту строку подставляется литеральное значение, а остальные следуют за текущим основным цветом через var().
Пресеты со светлым основным цветом (松花绿 (зелёный сосны) / 藤黄 (gamboge)) в осветлении не нуждаются, но всё равно получают тёмные переопределения --lh-primary-color-bg и --lh-selected-color.
Текст, генерируемый buildPresetStyleText, идёт в порядке: сначала блок :root { ... }, затем блок [data-theme='dark'], .dark { ... }. При равной специфичности побеждает последний, поэтому светлое значение --lh-selected-color, объявленное в :root у пресетов со светлым основным цветом, не просачивается в тёмную тему.
Это же — коренная причина правила «писать можно только <style>, а не встроенный style»: специфичность встроенного style 1,0,0,0 перекрывает весь блок тёмных переменных в var-dark.scss, и тёмная тема просто перестаёт работать (см. Обзор системы тем).
Пункт 7 проверки pnpm verify:styles (тёмная доступность): статическое сканирование, находящее токены, «запечённые в светлый цвет, но не имеющие переопределения в var-dark». Это CI-гейт.
pnpm verify:theme (AA 4.5:1): читает артефакты es/ и считает контрастность пресетов тем в светлой и тёмной версиях. Требует предварительного pnpm build; CI не запускается — выполняется вручную.
Визуальная проверка: переключить тёмную тему на сайте docs / в playground библиотек и просмотреть каждый компонент.
Чек-лист самопроверки при добавлении нового токена:
Должен ли этот токен перевернуться в тёмной теме?
Если да — добавлено ли переопределение в var-dark.scss?
Значение является интерполяцией #{$lh-*}? (Если да — тёмное переопределение обязательно)
Потребляется ли он в компоненте через цепочку var(--lh-x, fallback)?
Полнота тёмной темы в 0.4.0 прошла систематическое устранение пробелов (невидимый текст в меню / таблицах / таймлайне; см. changeset). Оставшиеся известные пункты ждут обработки вместе со «специальным выпуском по токенам высоты».