CSS Modules — это обычный CSS-файл с расширением .module.css, в котором имена классов по умолчанию локальны. Сборщик заменяет .button на уникальный .button_a1b2c, поэтому стили одного компонента не протекают в другой. Файл импортируется в JavaScript, а к классу вы обращаетесь как к свойству объекта.

/* Button.module.css */ .button { background: #7f56d9; color: #fff; } // Button.jsx import styles from './Button.module.css'; export default () => <button className={styles.button}>OK</button>; // В браузере: <button class="Button_button__a1b2c">OK</button>

Коротко о главном, если нужен только вывод:

  • Проблема, которую решают CSS Modules — глобальный скоуп классов в обычном CSS: любой .title виден всему проекту и легко переопределяется чужим стилем.
  • Механика — сборщик (Vite, webpack, Next.js) хеширует имена классов на этапе сборки и отдаёт в JS объект «локальное имя → уникальное имя».
  • Подключение — создать файл *.module.css и импортировать его; в Vite, Next.js и Create React App это работает из коробки, в чистом webpack нужен css-loader с опцией modules.
  • Расширения — composes для наследования классов, :global для точечного выхода в глобальную область, @value и CSS-переменные для токенов.
  • Альтернативы — CSS-in-JS, Tailwind, BEM; таблица сравнения и честные минусы ниже.

Разбор актуален на 2026 год: проверено по официальному репозиторию css-modules и документации Vite и Next.js 16.

Что такое CSS Modules и какую проблему они решают

По данным github.com.

Официальное определение из репозитория проекта: «CSS Module — это CSS-файл, в котором все имена классов и анимаций по умолчанию имеют локальную область видимости». Ключевое слово здесь — локальность. В обычном CSS все селекторы живут в одном глобальном пространстве имён. Стоит двум разработчикам объявить .card в разных файлах — и стили начинают перебивать друг друга в зависимости от порядка подключения. Отсюда растут !important, длинные цепочки селекторов и правило «не трогай этот CSS, всё сломается».

CSS Modules убирают саму причину: имя класса, которое вы пишете, — локальное. На сборке оно превращается в уникальный идентификатор, поэтому .card из одного компонента физически не может пересечься с .card из другого. Вы возвращаете себе короткие, осмысленные имена классов и перестаёте бояться коллизий.

Вывод раздела: CSS Modules — это не новый язык и не препроцессор, а соглашение об именовании плюс шаг сборки, который делает классы локальными по умолчанию.

ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросети DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
  • Где и как применять? Потестируем модель после установки на разных задачах
  • Как дообучить модель под себя?

Как работают CSS Modules: хеширование имён

Когда вы импортируете .module.css в JS-модуль, сборщик компилирует файл в формат ICSS (Interoperable CSS) и генерирует объект-словарь. Ключи — имена, которые вы написали в CSS; значения — уникальные строки, которые реально попадут в атрибут class.

/* styles.module.css */ .className { color: green; } // компонент import styles from './styles.module.css'; element.innerHTML = '<div class="' + styles.className + '">'; // styles.className === "styles_className__2tLf"

Конкретный вид хеша (например, styles_className__2tLf) задаётся шаблоном localIdentName в настройках css-loader и не важен для вашего кода: он лишь гарантированно уникален и связан с нужными правилами. Вы всегда обращаетесь к классу через объект styles, а не через строку.

Практический нюанс, о который спотыкаются на старте: если обратиться к несуществующему классу — styles.buttton с опечаткой — вы получите undefined без ошибки сборки, и элемент просто останется без стилей. Поэтому опечатки в CSS Modules ловятся глазами или TypeScript-типизацией, а не компилятором.

Синтаксис CSS Modules: import styles и styles.className

Базовый рабочий цикл состоит из трёх шагов: объявить класс в .module.css, импортировать файл дефолтным импортом (обычно под именем styles), подставить styles.className в атрибут className (React) или class.

/* Card.module.css */ .card { padding: 24px; border-radius: 12px; } .title { font: 600 18px Inter, sans-serif; } // Card.jsx import styles from './Card.module.css'; export default function Card() { return ( <div className={styles.card}> <h3 className={styles.title}>Заголовок</h3> </div> ); }

Несколько классов на одном элементе собираются шаблонной строкой или через удобный хелпер:

<button className={`${styles.button} ${styles.primary}`}>Купить</button>

Если имена классов в CSS написаны через дефис (.apply-color), сборщик по умолчанию оставляет их как есть, и обращаться придётся через скобки: styles['apply-color']. В Vite можно включить camelCase-конвертацию (localsConvention: 'camelCaseOnly') и импортировать классы поимённо: import { applyColor } from './x.module.css'.

Подключение CSS Modules: Vite, webpack, Next.js, CRA

Как CSS Modules изолирует классы: путь от .module.css до уникального class в браузере
Как CSS Modules изолирует классы: путь от .module.css до уникального class в браузере

Само расширение .module.css — это триггер: сборщик по нему понимает, что файл нужно обрабатывать как модуль. В большинстве современных инструментов настройка не требуется.

Vite

Файлы, оканчивающиеся на .module.css, распознаются как CSS Modules автоматически. Достаточно импортировать и использовать; поведение тонко настраивается через опцию css.modules в vite.config.

import classes from './example.module.css'; document.getElementById('foo').className = classes.red;

Next.js

Next.js (проверено на версии 16) поддерживает CSS Modules из коробки в директории app: создаёте *.module.css и импортируете в компонент. При этом глобальный CSS в Next.js ограничен — его рекомендуют держать только для действительно глобальных стилей, а компонентное оформление вести через модули.

/* app/blog/blog.module.css */ .blog { padding: 24px; } // app/blog/page.tsx import styles from './blog.module.css'; export default function Page() { return <main className={styles.blog}></main>; }

Create React App

CRA настроен на поддержку модулей без дополнительной конфигурации: создаёте файл с суффиксом .module.css, и всё работает.

Чистый webpack

Единственный случай, где нужна ручная настройка, — сборка на голом webpack. Здесь модули включает опция modules у css-loader:

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

Вывод раздела: в Vite, Next.js и CRA подключение сводится к расширению файла; ручная настройка лоадера остаётся только для чистого webpack.

composes: наследование классов в CSS Modules

Директива composes — аналог наследования: класс подтягивает правила другого класса, и на элемент навешиваются оба уникальных имени. Работает внутри файла, из другого файла и из глобального класса.

/* внутри одного файла */ .base { padding: 12px; border-radius: 8px; } .primary { composes: base; background: #7f56d9; color: #fff; } /* из другого файла */ .primary { composes: base from './shared.module.css'; } /* из глобального класса */ .primary { composes: btn from global; }

Два ограничения из документации: composes должна идти до остальных правил, и не стоит задавать одно и то же свойство в разных композируемых классах из разных файлов — порядок применения станет непредсказуемым. Внутри одного файла composes часто удобнее и прозрачнее, чем препроцессорный @extend.

:global и :local — выход в глобальную область

Иногда стиль должен остаться глобальным: класс от внешней библиотеки, разметка из стороннего виджета, тема на body. Для этого есть :global — он отключает локализацию для конкретного селектора или блока.

/* один селектор */ :global(.swiper-slide) { opacity: 1; } /* целый блок */ :global { .legacy-widget { color: #333; } }

Обратная директива :local(...) нужна редко — только если вы переключили лоадер в глобальный режим по умолчанию и хотите локализовать отдельные селекторы. В типовой связке с .module.css всё и так локально, поэтому вручную писать :local обычно не приходится.

Переменные и токены: @value и CSS custom properties

CSS Modules умеют экспортировать значения через @value — это переменные времени сборки, похожие на переменные в Sass или Less. Их можно объявить в отдельном файле и импортировать.

/* colors.module.css */ @value brand: #7f56d9; @value radius: 12px; /* button.module.css */ @value brand, radius from './colors.module.css'; .button { background: brand; border-radius: radius; }

На практике для тем и токенов сегодня чаще берут нативные CSS-переменные: они работают в рантайме, переключаются без пересборки и не требуют плагинов. @value и :root-переменные спокойно сочетаются в одном проекте.

/* global.css */ :root { --main-color: #7f56d9; } /* button.module.css */ .button { background-color: var(--main-color); }

Сравнение CSS Modules с альтернативами: обычный CSS, CSS-in-JS, Tailwind, BEM

CSS Modules — не единственный способ изолировать стили. Ниже — как подходы решают одну и ту же задачу «класс не должен протекать за пределы компонента».

Подход Изоляция стилей Где живут стили Сборка/рантайм Когда брать
Обычный CSS Нет, глобальный скоуп .css-файлы Ничего не нужно Мелкие сайты, статические страницы
CSS Modules Да, локально по умолчанию .module.css рядом с компонентом Хеширование на сборке, ноль рантайма Компонентные SPA на React/Vue/Svelte
CSS-in-JS Да, через генерацию классов В JS-файле компонента Часто рантайм-стоимость Динамические стили от пропсов и стейта
Tailwind Не нужна: утилиты вместо классов В разметке (className) Генерация утилит на сборке Быстрая вёрстка по дизайн-системе
BEM Дисциплиной именования, вручную Обычные .css-файлы Ничего не нужно Проекты без сборщика или legacy

Важное различие: BEM решает ту же проблему коллизий, что и CSS Modules, но силой соглашения — вы вручную пишете длинные .card__title--active и надеетесь, что команда не нарушит правило. CSS Modules дают ту же изоляцию автоматически, оставляя короткие имена. При этом BEM-именование внутри модулей никто не запрещает — подходы не конфликтуют.

Плюсы и минусы CSS Modules

Сильные стороны:

  • Локальный скоуп из коробки — коллизии имён исчезают как класс проблем.
  • Ноль рантайм-стоимости: вся работа делается на сборке, в браузер уходит обычный CSS.
  • Это по-прежнему настоящий CSS — работают медиазапросы, псевдоклассы, каскад, любые фичи.
  • Явные зависимости: видно, какой компонент какие стили импортирует, мёртвый CSS проще находить.

Слабые стороны, о которых честно стоит знать:

  • Опечатка в styles.xxx не вызывает ошибку — класс молча теряется. Лечится TypeScript-типизацией модулей.
  • Динамические стили «от значения пропса» неудобны — это территория CSS-in-JS или CSS-переменных.
  • Нужен сборщик: без шага сборки CSS Modules не работают в принципе.
  • Композиция глубже пары уровней composes читается тяжелее, чем плоский набор классов.

Вывод: CSS Modules — прагматичный дефолт для компонентных приложений, где нужна изоляция без рантайм-налога и без ухода от обычного CSS.

Частые вопросы про CSS Modules

Чем CSS Modules отличаются от обычного CSS?

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

Нужен ли сборщик для CSS Modules?

Да. CSS Modules — это шаг сборки. В Vite, Next.js и Create React App поддержка встроена, для чистого webpack включается опция modules у css-loader. Без сборщика механизм не работает.

Как подключить CSS Modules в Vite?

Создайте файл с расширением .module.css и импортируйте его: import styles from './x.module.css'. Vite распознаёт такие файлы автоматически, дополнительная настройка не нужна.

Можно ли использовать CSS Modules без React?

Да. Механизм не привязан к React — он работает с Vue, Svelte, Angular и даже с ванильным JS, потому что импорт отдаёт обычный объект «имя класса → строка».

CSS Modules или Tailwind — что выбрать?

Это разные инструменты. Tailwind — утилитарные классы в разметке для быстрой вёрстки по дизайн-системе; CSS Modules — изоляция ваших собственных классов. Их часто совмещают: Tailwind на типовое, модули на сложные компонентные стили.

Как избежать опечаток в именах классов?

Подключите генерацию TypeScript-типов для .module.css (плагины вроде typed-css-modules или встроенная поддержка в сборщике). Тогда обращение к несуществующему классу подсветит редактор ещё до запуска.

Большой практикум
ЗАМЕНИ ВСЕ НЕЙРОСЕТИ НА ОДНУ — PERPLEXITY
ПОКАЖЕМ НА КОНКРЕТНЫХ КЕЙСАХ
  • Освой нейросеть Perplexity и узнай, как пользоваться функционалом остальных ИИ в одном
  • УЧАСТВОВАТЬ ЗА 0 РУБ.
  • Расскажем, как получить подписку
Участвовать бесплатно
ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросеть DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
Участвовать бесплатно