Глобальные и локальные стили при использовании CSS Modules

CSS Modules в экосистеме Webpack позволяют изолировать стили на уровне компонентов, исключая глобальные конфликты классов и обеспечивая предсказуемость каскада. Однако при практической разработке неизбежно возникает необходимость сочетать локальную область видимости с глобальными стилями: reset/normalize, типографика, переменные темы, utility-классы, сторонние библиотеки. В результате формируется гибридная модель, в которой CSS Modules и глобальные стили сосуществуют в одном проекте.

CSS Modules преобразуют каждый CSS-класс в уникальный идентификатор на этапе сборки. Это происходит через loader в Webpack, чаще всего через связку css-loader с включённой опцией modules.

Исходный CSS:

.button {
  padding: 10px;
  background: blue;
}

После обработки:

.button__3x9aK {
  padding: 10px;
  background: blue;
}

JS-код получает доступ к сгенерированному имени через объект:

import styles from './button.module.css';

element.className = styles.button;

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

Глобальная область видимости в рамках CSS Modules

Несмотря на локализацию, CSS Modules не запрещают создание глобальных правил. В css-loader предусмотрена директива :global, которая отключает модульное преобразование для выбранных селекторов.

:global(.container) {
  max-width: 1200px;
  margin: 0 auto;
}

Такой класс будет доступен во всём приложении без изменения имени.

Также возможно глобализировать вложенные блоки:

.wrapper {
  padding: 20px;
}

.wrapper :global(.external-class) {
  color: red;
}

Здесь .wrapper остаётся локальным, а .external-class — глобальным.

Глобальные стили вне модулей

В большинстве архитектур Webpack глобальные стили выделяются в отдельные файлы, которые не проходят через modules: true. Обычно это:

  • reset.css или normalize.css
  • базовая типографика
  • переменные CSS (если не используются CSS Modules variables)
  • стили сторонних библиотек
  • layout-основа приложения

Пример конфигурации Webpack:

module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }
        ]
      },
      {
        test: /\.css$/,
        exclude: /\.module\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

Такое разделение формирует две параллельные системы:

  • *.module.css → локальная область видимости
  • *.css → глобальная область видимости

Конфликтные зоны между глобальными и локальными стилями

Основная сложность возникает при пересечении глобального и локального каскада. Несмотря на изоляцию имён, CSS остаётся каскадным языком, и специфичность сохраняется.

Перекрытие через специфичность

Глобальный стиль:

.button {
  background: red;
}

Локальный стиль:

.button {
  background: blue;
}

После трансформации локальный класс становится уникальным:

.button__a1b2c {
  background: blue;
}

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

<div className={`button ${styles.button}`} />

В этом случае применяется каскад, и итоговый результат зависит от порядка подключения стилей в DOM.

Стратегии организации глобальных стилей

Базовый слой (Base Layer)

Глобальные стили часто структурируются как фундамент, подключаемый первым:

html {
  font-size: 16px;
}

body {
  margin: 0;
  font-family: system-ui;
}

Эти правила не должны зависеть от компонентов и не должны переопределяться локальными стилями.

Utility-классы

Глобальные utility-классы используются для повторяемых мелких задач:

.mt-10 {
  margin-top: 10px;
}

.text-center {
  text-align: center;
}

Они остаются глобальными по дизайну, так как их задача — перекрёстное использование.

Темизация через CSS variables

Глобальные переменные позволяют разделить логику и оформление:

:root {
  --primary-color: #1e90ff;
  --font-size-base: 16px;
}

В модулях они используются без утраты локальности:

.title {
  color: var(--primary-color);
  font-size: var(--font-size-base);
}

Смешивание глобальных и локальных стилей

В реальных проектах CSS Modules почти никогда не используются изолированно. Типичная архитектура включает гибридный подход:

  • глобальные стили задают основу интерфейса
  • CSS Modules отвечают за компоненты
  • utility-классы обеспечивают быстрые правки без создания новых модулей

Пример компонента:

import styles from './card.module.css';

export function Card() {
  return (
    <div className={`card ${styles.card}`}>
      <h2 className="text-center">Title</h2>
    </div>
  );
}

Здесь:

  • card — глобальный класс (например, layout или reset-стиль)
  • styles.card — локальная стилизация компонента
  • text-center — utility-класс

Приоритет и каскад в гибридной системе

Каскад становится многослойным:

  1. Браузерные стили
  2. Reset/normalize
  3. Глобальные базовые стили
  4. Utility-классы
  5. Сторонние библиотеки
  6. Глобальные классы приложения
  7. CSS Modules (локальные стили)

Однако фактический порядок может меняться в зависимости от порядка импорта в Webpack и подключения через style-loader.

Важно учитывать, что CSS Modules не изменяют каскадность языка, они только модифицируют селекторы.

Практика организации файлов

Типичная структура проекта:

styles/
  base.css
  reset.css
  variables.css
  utilities.css

components/
  Button/
    button.module.css
    Button.jsx

Подключение глобальных стилей обычно выполняется один раз в entry-файле:

import './styles/reset.css';
import './styles/base.css';
import './styles/variables.css';
import './styles/utilities.css';

Псевдо-глобальность через композицию

CSS Modules позволяют частично эмулировать глобальные стили через composes:

.base {
  padding: 10px;
  border-radius: 4px;
}

.primary {
  composes: base;
  background: blue;
}

Это создаёт локальную композицию без выхода в глобальный scope, сохраняя изоляцию.

Сторонние библиотеки и глобальный scope

Многие UI-библиотеки используют глобальные классы. При интеграции с CSS Modules возникает необходимость явного взаимодействия:

<div className={`${styles.wrapper} external-lib-class`} />

Иногда требуется переопределение:

:global(.external-lib-class) {
  margin: 0;
}

Такой подход используется осторожно, так как он нарушает изоляцию модулей.

Ошибки архитектуры при смешении стилей

Частые проблемы возникают при неправильном распределении областей видимости:

  • перенос логики компонента в глобальные стили
  • использование глобальных классов вместо модулей
  • чрезмерное использование :global
  • дублирование utility-классов в модулях
  • зависимость компонентов от порядка подключения CSS

Каждая из этих проблем приводит к потере предсказуемости каскада и усложняет поддержку системы стилей.

Согласованная модель разделения

Корректная архитектура строится на чётком разделении:

  • глобальные стили отвечают за окружение приложения
  • CSS Modules отвечают за компоненты
  • utility-классы используются как вспомогательный слой
  • :global применяется только для интеграции, а не для основной логики

Такая структура позволяет сохранять предсказуемость каскада при одновременном использовании глобальных и локальных стилей в Webpack-проектах.