Вёрстка сайта с использованием CSS Modules
CSS Modules решают одну конкретную проблему — глобальное пространство имён CSS. В любом проекте больше трёх разработчиков или больше ста компонентов классы начинают конфликтовать, переопределять друг друга, и !important расползается по кодовой базе. CSS Modules дают локальную область видимости на уровне сборки — без runtime-overhead, без shadow DOM.
Принцип работы
Vite, Webpack, Parcel и другие бандлеры трансформируют CSS Modules на этапе сборки. Имя класса в .module.css файле хешируется:
.button → .button_a3f7k2x
.title → .title_9cxm1pq
Итоговый HTML содержит хешированные имена, конфликт невозможен по определению.
/* Button.module.css */
.root {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background-color: var(--color-accent);
color: #fff;
border-radius: 0.5rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: background-color 150ms ease, transform 100ms ease;
}
.root:hover {
background-color: var(--color-accent-hover);
}
.root:active {
transform: scale(0.98);
}
.root:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Варианты */
.ghost {
background-color: transparent;
color: var(--color-accent);
border: 1px solid var(--color-accent);
}
.ghost:hover {
background-color: var(--color-accent);
color: #fff;
}
/* Размеры */
.sm {
padding: 0.25rem 0.75rem;
font-size: 0.875rem;
}
.lg {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
// Button.tsx
import { FC, ButtonHTMLAttributes } from 'react';
import styles from './Button.module.css';
import clsx from 'clsx';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'ghost';
size?: 'sm' | 'md' | 'lg';
}
const Button: FC<ButtonProps> = ({
variant = 'default',
size = 'md',
className,
children,
...props
}) => {
return (
<button
className={clsx(
styles.root,
variant === 'ghost' && styles.ghost,
size === 'sm' && styles.sm,
size === 'lg' && styles.lg,
className
)}
{...props}
>
{children}
</button>
);
};
Настройка в Vite
CSS Modules работают из коробки — любой файл *.module.css обрабатывается автоматически:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
css: {
modules: {
// camelCase для доступа из JS: styles.myClass вместо styles['my-class']
localsConvention: 'camelCase',
// Формат генерируемых имён классов
generateScopedName:
process.env.NODE_ENV === 'production'
? '[hash:base64:8]'
: '[name]__[local]__[hash:base64:4]',
},
},
});
Паттерн: composes для переиспользования
CSS Modules поддерживают composes — наследование стилей без JavaScript:
/* base.module.css */
.flexCenter {
display: flex;
align-items: center;
justify-content: center;
}
.card {
background: var(--color-bg-surface);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
}
/* Hero.module.css */
.container {
composes: flexCenter from './base.module.css';
flex-direction: column;
min-height: 100svh;
text-align: center;
}
.card {
composes: card from './base.module.css';
max-width: 480px;
padding: 2rem;
}
Глобальные стили рядом с модульными
Иногда нужно пробить модульную область видимости — для сторонних библиотек или состояний из внешнего источника:
/* Modal.module.css */
.overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.5);
}
/* :global() — класс НЕ хешируется */
:global(.is-open) .overlay {
display: flex;
}
/* Стили для дочерних элементов Swiper */
.slider :global(.swiper-pagination-bullet-active) {
background-color: var(--color-accent);
}
TypeScript: типизированные CSS Modules
Генерация .d.ts файлов для автодополнения:
npm install -D typescript-plugin-css-modules
// tsconfig.json
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-plugin-css-modules"
}
]
}
}
Или через vite-plugin-dts + typed-css-modules:
npx tcm src --camelCase --pattern '**/*.module.css'
Результат — автодополнение в IDE при import styles from './Component.module.css'.
Структура файлов
src/
components/
Button/
Button.tsx
Button.module.css
Button.test.tsx
index.ts ← re-export
Card/
Card.tsx
Card.module.css
index.ts
styles/
globals.css ← глобальные сбросы, шрифты, :root переменные
tokens.css ← design tokens
pages/
HomePage/
HomePage.tsx
HomePage.module.css
Сравнение с альтернативами
| CSS Modules | Tailwind | Styled Components | BEM | |
|---|---|---|---|---|
| Локальная область | Да | Да (через утилиты) | Да | Нет |
| Размер bundle | Минимальный | Минимальный | +12 KB runtime | Минимальный |
| DX | Хорошо | Отлично | Хорошо | Средне |
| Переиспользование | composes | className composition | props | modifiers |
| SSR | Да | Да | Требует настройки | Да |
CSS Modules оптимальны для команд, предпочитающих «настоящий CSS» без синтаксического сахара Tailwind и без runtime CSS-in-JS.
Сроки
Настройка CSS Modules в Vite/Webpack-проекте: 1–2 часа. Вёрстка страницы с нуля на CSS Modules: темп аналогичен обычному CSS, без overhead. Миграция компонентов с глобального CSS: ~30 минут на компонент с учётом рефакторинга имён классов.







