Sass и пользовательские свойства для тем
Переменные Sass исчезают после компиляции, поэтому переключить тему на открытой странице они не могут. Пользовательские свойства CSS могут — но писать десятки свойств вручную для каждой темы утомительно. Лучшее от обоих подходов: хранить темы в картах Sass, а цикл пусть превращает их в пользовательские свойства.
Темы в карте
Каждая тема — это карта из имён токенов и цветов. Создайте styles/themes/_themes.scss:
// styles/themes/_themes.scss
$themes: (
"light": (
"bg": #ffffff,
"text": #1f2937,
"accent": #2563eb,
),
"dark": (
"bg": #111827,
"text": #f9fafb,
"accent": #60a5fa,
),
);Генерация пользовательских свойств
Миксин принимает одну тему и выводит пользовательское свойство для каждого токена. Имя свойства собирает интерполяция --#{$name}:
// styles/themes/_index.scss
@use "sass:map";
@use "themes";
@mixin theme-vars($theme) {
@each $name, $value in $theme {
--#{$name}: #{$value};
}
}
:root {
@include theme-vars(map.get(themes.$themes, "light"));
}
[data-theme="dark"] {
@include theme-vars(map.get(themes.$themes, "dark"));
}Светлая тема попадает в :root — она действует по умолчанию. Тёмная включается, когда у элемента есть атрибут data-theme="dark".
Компоненты используют var()
Компоненты больше не знают цветов — они читают пользовательские свойства. Эти значения вычисляются в браузере, поэтому следуют текущей теме:
// styles/components/_cards.scss
.card {
padding: 16px;
color: var(--text);
background: var(--bg);
border: 2px solid var(--accent);
}// styles/main.scss
@use "themes";
@use "components/cards";:root {
--bg: #ffffff;
--text: #1f2937;
--accent: #2563eb;
}
[data-theme=dark] {
--bg: #111827;
--text: #f9fafb;
--accent: #60a5fa;
}
.card {
padding: 16px;
color: var(--text);
background: var(--bg);
border: 2px solid var(--accent);
}Sass убрал кавычки в [data-theme=dark] — для браузера это тот же селектор.
Переключение темы
Один атрибут на <html> переключает все цвета. Скрипт только меняет атрибут, все цвета остаются в CSS:
<style>
:root {
--bg: #ffffff;
--text: #1f2937;
--accent: #2563eb;
}
[data-theme=dark] {
--bg: #111827;
--text: #f9fafb;
--accent: #60a5fa;
}
.card {
padding: 16px;
color: var(--text);
background: var(--bg);
border: 2px solid var(--accent);
}
</style>
<div class="card">
<p>Карточка следует за темой.</p>
<button type="button" id="toggle">Сменить тему</button>
</div>
<script>
document.querySelector('#toggle').addEventListener('click', () => {
const root = document.documentElement;
root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
});
</script>Нажмите кнопку: фон, текст и рамка карточки меняются разом, без повторной компиляции.
Кто что делает
| Задача | Инструмент |
|---|---|
| Хранить темы и токены, проверять имена | Карты и функции Sass |
| Сгенерировать переменные для каждой темы | @each в Sass |
| Переключить тему на странице | Пользовательские свойства CSS |
| Прочитать цвет в компоненте | var(--accent) |
Цветовые функции Sass здесь тоже помогают: например, оттенок для наведения можно вычислить через color.adjust() для каждой темы ещё при составлении карты.
Частая ошибка
Функции Sass не умеют работать с var(). color.adjust(var(--accent), $lightness: -10%) останавливается с ошибкой: при компиляции var(--accent) — просто текст, его цвет известен только в браузере. Вычисляйте оттенки в Sass до того, как они станут пользовательскими свойствами, или используйте функции CSS вроде color-mix(), которые работают в браузере.