Vanilla Extract

Vanilla Extract — это CSS-in-JS библиотека для создания типобезопасных, масштабируемых стилей в приложениях на JavaScript и TypeScript. В сочетании с Radix UI она позволяет аккуратно разделять логику компонентов и оформление, сохраняя строгую типизацию и поддержку темизации.

Radix UI предоставляет примитивные компоненты с полностью контролируемым стилевым слоем. Использование Vanilla Extract делает процесс стилизации этих компонентов прозрачным и удобным, обеспечивая совместимость с CSS-переменными и модульной архитектурой.


Установка и настройка

Для работы с Vanilla Extract необходимо установить пакет:

npm install @vanilla-extract/css

Для TypeScript проектов также требуется подключить типы:

npm install --save-dev @types/node

Конфигурация сборщика (Vite, Webpack) должна поддерживать vanilla-extract:

  • Vite: подключается плагин @vanilla-extract/vite-plugin.
  • Webpack: используется @vanilla-extract/webpack-plugin вместе с css-loader.

Пример для Vite:

import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';

export default {
  plugins: [vanillaExtractPlugin()],
};

Создание стилей

Vanilla Extract использует style и recipe для определения базовых и динамических стилей.

import { style, recipe } from '@vanilla-extract/css';

export const button = style({
  padding: '10px 20px',
  borderRadius: '4px',
  fontWeight: 'bold',
  cursor: 'pointer',
});

export const buttonVariants = recipe({
  base: button,
  variants: {
    color: {
      primary: { backgroundColor: 'blue', color: 'white' },
      secondary: { backgroundColor: 'gray', color: 'black' },
    },
    size: {
      small: { fontSize: '12px', padding: '6px 12px' },
      large: { fontSize: '18px', padding: '12px 24px' },
    },
  },
  defaultVariants: {
    color: 'primary',
    size: 'small',
  },
});

Ключевые моменты:

  • style — создание простого класса с фиксированными стилями.
  • recipe — создание вариативного класса с возможностью динамически менять стили через пропсы.
  • defaultVariants — определяет базовые варианты для компонента.

Интеграция с Radix UI

Radix UI предоставляет примитивы без собственных стилей (unstyled primitives). Vanilla Extract идеально подходит для их стилизации.

Пример кнопки с Radix UI Button:

import * as RadixButton from '@radix-ui/react-button';
import { buttonVariants } from './styles.css';

export const MyButton = ({ color, size, ...props }) => {
  return (
    <RadixButton.Root className={buttonVariants({ color, size })} {...props}>
      {props.children}
    </RadixButton.Root>
  );
};

Особенности:

  • Можно создавать несколько вариантов стилей для одного примитива Radix.
  • Сохранение всех свойств Radix UI без потери типизации.
  • Простое управление состояниями, например disabled или active, через дополнительные variants.

Темизация и CSS-переменные

Vanilla Extract поддерживает CSS-переменные, что позволяет создавать темы.

import { createTheme, style } from '@vanilla-extract/css';

export const lightTheme = createTheme({
  color: {
    background: 'white',
    text: 'black',
    primary: 'blue',
  },
});

export const darkTheme = createTheme({
  color: {
    background: '#1a1a1a',
    text: 'white',
    primary: 'purple',
  },
});

export const card = style({
  backgroundColor: 'var(--color-background)',
  color: 'var(--color-text)',
  padding: '16px',
  borderRadius: '8px',
});

Использование темы с Radix UI:

import { darkTheme, card } from './themes.css';

export const DarkCard = () => {
  return (
    <div className={darkTheme}>
      <div className={card}>Темная карточка</div>
    </div>
  );
};

Работа с состояниями

Vanilla Extract позволяет описывать состояния через selectors и variants:

export const toggleButton = recipe({
  base: {
    padding: '10px',
    borderRadius: '4px',
    border: '1px solid gray',
    cursor: 'pointer',
    selectors: {
      '&:hover': {
        backgroundColor: 'lightgray',
      },
    },
  },
  variants: {
    active: {
      true: { backgroundColor: 'blue', color: 'white' },
      false: { backgroundColor: 'white', color: 'black' },
    },
  },
});

Интеграция с Radix UI Toggle:

import * as RadixToggle from '@radix-ui/react-toggle';
import { toggleButton } from './styles.css';

export const MyToggle = ({ active, ...props }) => {
  return (
    <RadixToggle.Root className={toggleButton({ active })} {...props}>
      {props.children}
    </RadixToggle.Root>
  );
};

Преимущества использования Vanilla Extract с Radix UI

  1. Полная типизация: ошибки в именах классов и свойств выявляются на этапе компиляции.
  2. Модульность: стили для компонентов легко импортировать и использовать без глобальных конфликтов.
  3. Динамические варианты: возможность создавать универсальные компоненты с изменяемыми стилями.
  4. Темизация: легко поддерживать светлую и темную темы с CSS-переменными.
  5. Интеграция с Radix UI: можно использовать все примитивы без ограничения стилизации, сохраняя реактивность и доступность.

Лучшие практики

  • Использовать recipe для компонентов с несколькими вариантами стиля.
  • Стараться хранить темы и глобальные переменные отдельно, чтобы их было легко менять.
  • Для Radix UI компонентов выделять отдельный файл стилей с Vanilla Extract, не смешивая с логикой.
  • Применять CSS-переменные для цветов, отступов и типографики — это упрощает масштабирование проекта.

Vanilla Extract с Radix UI создаёт мощный стек для современных приложений: строго типизированные, стилизуемые, доступные компоненты, которые легко поддерживать и расширять.