August 25, 2022

Организация SCSS: цвета

Удобно в SCSS сделать переменные для основных цветов приложения (палитра), и потом использовать везде понятные названия, вместо кодов цветов. Статья описывает современный универсальный и гибкий способ организации с работы с цветами, основанный на модулях и @use вместо устаревшего @import.

Для начала опишу как примерно выглядит “старый” подход. Создается файл _variables.scss и в нем определяется куча переменных на все случаи жизни, в том числе и переменные для цветов:

// _variables.scss
...

// Colors
$color-primary: #6750A4;
$color-background: #EADDFF;

...

Далее, в корневой SCSS-файл (entry-point-файл) сначала импортируется этот файл с переменными, а затем файлы с различными компонентами:

// styles.scss

@import './variables';
@import './my-component';
@import './other-component';
...

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

// _my-component.scss

.my-component {
  color: $color-primary;
  background-color: $color-background;
}

Примерно такой подход я встречал на большинстве проектов. Например, Bootstrap все еще использует такой подход – https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss

В целом, схема рабочая. Но мне не нравится этот огромный файл с переменными. И не нравится что все переменные глобальные.

Другой подход я подсмотрел в реализации Material Components for the web. И теперь на всех проектах использую только его. Здесь нет единого огромного файла _variables.scss. Вместо этого переменные располагаются контекстно, рядом с модулями, к которым они относятся.

Организауем все цвета приложения в виде модуля colors:

// theme/_colors.scss

$primary: #01579B !default;
$background: #E1F5FE !default;

Кстати, так как переменные уже не будут глобальными, можно использовать более простые и короткие имена, не опасаясь что они совпадут с какими-то другими переменными из других файлов. Я предпочитаю класть этот модуль в папку theme, позже объясню зачем.

Далее, в файле, в котором нужны цвета, подключаем этот модуль и используем переменные с указанием модуля:

// _my-component.scss
@use './theme/colors';

.my-component {
  color: colors.$primary;
  background-color: colors.$background;
}

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

  1. Теперь нет одного огромного файла _variables.scss со всеми переменными. Соответственно код уже не монолит, а набор связанных модулей.
  2. Сразу понятно от каких других модулей зивисит данный конкретный файл (компонент).
  3. Переменные используются с префиксом (namespace), по которому видно из какого именно модуля пришла та или иная переменная.

Бонус: поддержка нескольких тем (themes)

Допустим, у приложения есть две темы: обычная и контрастная.

Делаем для них соответствующие подпапки в папке theme. И добавляем в каждую по _index.scss:

В theme/default-theme/_index.scss подключаем все тот же модуль colors, который лежит папкой выше. Но подключаем с переопределением значений переменных в модуле, тоесть устанавливаем цвета для обычной темы:

// theme/default-theme/_index.scss
@use '../colors' with (
  $primary: #006783,
  $background: #bde9ff
);

А в контрастной, например, устанавливаем цвета с большим уровнем контрастности:

// theme/contrast-theme/_index.scss
@use '../colors' with (
  $primary: #000000,
  $background: #ffffff
);

Важно понимать – команда @use загружает модуль только один раз, когда встречает его первый раз. Поэтому нужно что бы самым первым встретился @use colors, который настраивает цвета для данной темы. Все последующие @use colors, которые будут встречаться в файлах компонентов, будут использовать эти переопределенные цвета.

Для каждой темы нужен будет отдельный entry-point-файл. В начале такого файла подключаем тему:

// styles-default.scss

// В самом начале файла подключаем нужную тему, которая настроит цвета в colors.
@use './theme/normal-theme';

// Далее подключаем весь остальной код, который будет общий для обоих тем
@use './my-component';
// styles-contrast.scss

// В самом начале файла подключаем нужную тему, которая настроит цвета в colors.
@use './theme/contrast-theme';

// Далее подключаем весь остальной код, который будет общий для обоих тем
@use './my-component';

Способ использования этих тем уже зависит от конкретного приложения. Можно например сгенерить отдельные CSS-файлы и переключаться между ними.