Оптимизация UI-кита: стейты компонентов на CSS-переменных

При верстке UI-компонентов легко погрязнуть в рутине. Когда я создавал очередной UI-кит, я наткнулся на раздражающую закономерность: дизайнеры нарисовали для обычной кнопки примерно пять цветовых тем. И для каждой мне приходилось вручную дублировать селекторы состояний: hover, active, disabled.

CSS .base-btn--theme-secondary:hover { /* стили */ } .base-btn--theme-secondary:active { /* стили */ }

.base-btn--theme-outlined:hover { /* стили */ } .base-btn--theme-outlined:active { /* стили */ } ...и так до бесконечности для каждой темы

Конечно, можно использовать миксины в SCSS, но они лишь маскируют проблему: на выходе в бандле мы все равно получаем раздутый статичный CSS.

Тогда я пришел к декларативному подходу на CSS Custom Properties. Идея в том, чтобы описать поведение ховеров и кликов на базовом уровне один раз, привязав свойства к переменным:

CSS .base-btn {   /* 1. Декларируем стейт-переменные по умолчанию */   --text-color: var(--n-n00);   --background-color: var(--a-a32);     /* 2. Привязываем свойства один раз */   color: var(--text-color);   background-color: var(--background-color); }

/* 3. Описываем реакцию на события один раз для ВСЕХ тем */ .base-btn:hover {   --text-color: var(--text-color-hover);   --background-color: var(--background-color-hover); } .base-btn:active {   --text-color: var(--text-color-active);   --background-color: var(--background-color-active); }

Теперь, чтобы создать новую тему (например, outlined), нам больше не нужно писать селекторы событий. Мы просто переопределяем значения самих переменных в один селектор:

CSS .base-btn--theme-outlined {   --text-color: var(--a-a32);   --background-color: transparent;

--text-color-hover: var(--n-n00);   --background-color-hover: var(--a-a30);

--text-color-active: var(--n-n00);   --background-color-active: var(--a-a35); }

Где это особенно спасает:  1. Глубокая вложенность: Если внутри кнопки нужно перекрасить SVG-иконку по ховеру, вам не нужно писать .base-btn:hover .icon. Достаточно внутри .icon задать fill: var(--text-color).  2. Динамическая смена тем (Dark Mode): Достаточно поменять значения базовых токенов на корневом элементе, и все компоненты мгновенно перестроят свои ховер-состояния в рантайме.

О чем стоит помнить: Если у вашего компонента слишком много состояний (например, focus-visible, loading, error, selected), конфигурация переменных на верхнем уровне может сильно разрастись.

А как вы боретесь с дублированием стилей состояний в UI-китах? Используете рантайм-переменные или доверяете препроцессорам?