Темизация

Тема в Koobiq — это набор CSS-переменных. Смена темы меняет значения переменных, всё остальное остаётся как есть. Здесь описано, как подключить тему, как её переключать и как брать её значения в своих стилях.

Есть три уровня.

Глобальные переменные описывают дизайн-систему: цвета, размеры, шрифты. Они приходят из пакета @koobiq/design-tokens и объявлены под селектором темы — .kbq-light или .kbq-dark. Именно ими вы пользуетесь в своих стилях.

Переменные компонента нужны внутри компонентов Koobiq. Имя каждой начинается с названия компонента (--kbq-button-*, --kbq-alert-*), а значение ссылается на глобальную переменную. Компонент объявляет их сам, поэтому отдельно подключать ничего не нужно. Исключение — code-block, scrollbar и skeleton: их токены лежат в пакете @koobiq/design-tokens, но при обычном подключении и для них ничего делать не нужно — значения уже входят в файл готовых стилей.

Ваши стили обращаются к глобальным переменным напрямую. Ни импортов, ни миксинов, ни регистрации в едином источнике темы.

Подключить тему — значит поставить класс на <body>. Всё, что внутри, и компоненты, и ваша вёрстка, подхватит новые значения само, потому что читает одни и те же переменные.

  • Установите пакет Koobiq.
  • Подключите файлы css-tokens.css, css-tokens-light.css и css-tokens-dark.css — в них лежат глобальные значения.
  • Подключите файл готовых стилей в свой основной файл. Без него компоненты и всплывающие элементы (окна, выпадающие списки) отобразятся неправильно. В нём же лежат и значения токенов code-block, scrollbar и skeleton, поэтому отдельные файлы для них подключать не нужно:
@use '@koobiq/components/prebuilt-themes/theme.css';
  • Добавьте класс темы на <body>:
<body class="kbq-app-background kbq-light">
    <app></app>
</body>

Класс kbq-app-background красит саму страницу — фон и цвет текста.

  • Импортируйте компонент и пользуйтесь. 🚀

Для этого есть KbqThemeService. Он ставит нужный класс на <body>, запоминает выбор и в режиме 'auto' следует системной цветовой схеме.

Режима три: 'light', 'dark' и 'auto'. По умолчанию — 'auto'. Системная цветовая схема поддерживается из коробки: подписываться на matchMedia вручную не нужно.

Текущее состояние сервис отдаёт сигналами, их можно читать прямо в шаблоне:

protected readonly themeService = inject(KbqThemeService);
<button kbq-button (click)="themeService.toggle()">
    {{ themeService.colorScheme() === 'dark' ? 'Светлая тема' : 'Тёмная тема' }}
</button>

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

ТемаСелектор
Светлая.kbq-light
Тёмная.kbq-dark

Настройки передаются при запуске приложения:

import { kbqThemeProvider } from '@koobiq/components/core';

bootstrapApplication(AppComponent, {
    providers: [kbqThemeProvider({ mode: 'auto', storageKey: 'my-app-theme' })]
});

Выбор темы сохраняется между визитами — по умолчанию в localStorage, причём смена темы в одной вкладке подхватывается остальными открытыми вкладками приложения. Приложения, которые собираются на сервере, могут хранить его в куке: тогда сервер сразу знает, какую тему рисовать. Для этого подключите KbqThemeCookieStore через токен KBQ_THEME_STORE — он читает куку прямо из входящего запроса, больше на сервере ничего настраивать не нужно.

Напишите переменную там, где нужно значение:

.my-component-text {
    color: var(--kbq-foreground-contrast-secondary);
}

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

Current theme: light

Полный список глобальных переменных — на странице Дизайн-токены.

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

/* button-tokens.scss */
.kbq-button,
.kbq-button-icon {
    --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-contrast);
}

Кнопка задаёт переменную на себе, поэтому общее объявление в :root она просто не увидит. Чтобы переопределение сработало, нужно попасть в тот же самый элемент.

Отсюда два рабочих способа.

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

<button kbq-button class="my-danger-button">Удалить</button>

<style>
    .my-danger-button {
        --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error);
    }
</style>

Поменять все компоненты этого типа в приложении. Здесь нужен селектор потяжелее, чем у самого компонента. Проще всего добавить перед ним селектор темы: заодно можно задать разные значения для светлой и тёмной, если один цвет на обе не годится:

.kbq-light .kbq-button {
    --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error);
}

.kbq-dark .kbq-button {
    --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error-fade);
}

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

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

@Component({
    selector: 'my-card',
    templateUrl: './my-card.html',
    styleUrl: './my-card.scss',
    encapsulation: ViewEncapsulation.None,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyCard {}
.my-card {
    padding: var(--kbq-size-l);
    border: var(--kbq-size-border-width) solid var(--kbq-line-contrast-less);
    border-radius: var(--kbq-size-border-radius);

    background: var(--kbq-background-card);
    color: var(--kbq-foreground-contrast);
}

Отдельный миксин _my-card-theme.scss, который потом нужно регистрировать в едином источнике темы, писать не надо.

Где браузер поддерживает corner-shape, компоненты скругляют углы сквирклом (corner-shape: squircle), в остальных браузерах — обычным радиусом. Сквиркл выглядит мельче дуги того же радиуса, поэтому радиус для него увеличивается. Чтобы углы вашего компонента совпадали с библиотечными, подключите миксин:

@use '@koobiq/components/core/styles/common/squircle' as *;

.my-card {
    @include kbq-squircle(var(--kbq-size-border-radius));
}

Без SCSS то же правило пишется вручную:

.my-card {
    --my-card-radius: var(--kbq-size-border-radius);

    border-radius: var(--my-card-radius);
}

@supports (corner-shape: squircle) {
    .my-card {
        corner-shape: squircle;
        border-radius: clamp(
            var(--my-card-radius) * 1.85,
            var(--my-card-radius) * 2.3 - 3.6px,
            var(--my-card-radius) * 2
        );
    }
}

Радиус компонента библиотеки меняйте через его переменную (например, --kbq-button-size-border-radius), а не через border-radius. Увеличивается именно переменная: с голым border-radius угол останется сквирклом и будет выглядеть слишком мелким. Компоненту, скруглённому в круг через 50%, нужно ещё corner-shape: round.

Оформление всего приложения меняется в одном месте:

.kbq-light {
    --kbq-background-contrast: #1a3a6b;
    --kbq-foreground-contrast: #0d1b2a;
}

.kbq-dark {
    --kbq-background-contrast: #7ba7e8;
    --kbq-foreground-contrast: #e8eef7;
}

Подключите этот файл после файлов дизайн-системы — и все компоненты подхватят новые значения.

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

Глобальные переменные, со значениями и образцами: Дизайн-токены.

Переменные компонента объявлены рядом с его кодом, в файле токенов, например button-tokens.scss у кнопки: откройте его по ссылке Исходный код на странице компонента. В браузере инструменты разработчика показывают их на элементе компонента как CSS-переменные вида --kbq-button-*.

Токены дизайн-системы — это значения, которые задают внешний вид наших компонентов. Они лежат в пакете @koobiq/design-tokens.

Переменные компонентов — это значения, которые используются в стилях компонентов. Они получены из токенов дизайн-системы и лежат в репозитории @koobiq/components, рядом с самими компонентами.

Обратите внимание

Версия 4.0.0 пакета @koobiq/design-tokens удалила токены компонентов — кроме code-block, scrollbar и skeleton. Их токены поставляются отдельными файлами (component-tokens.css и его светлая и тёмная части), и их значения уже входят в prebuilt-themes/theme.css пакета @koobiq/components, так что подключать их самостоятельно не нужно. Остальные значения компоненты везут сами. Если копии удалённых токенов остались в ваших css-tokens.css, css-tokens-light.css, css-tokens-dark.css или css-tokens-font.css — удалите их, а затем запустите npx koobiq-tokens-codemod src, чтобы найти остальное, что затронуло обновление.

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