CSS Modules в SPA
CSS Modules — система локального скопирования CSS-классов: сборщик автоматически генерирует уникальные имена классов, устраняя конфликты стилей между компонентами.
Зачем нужно
В крупном SPA глобальные CSS-классы вступают в конфликт — .button в одном компоненте перекрывает .button в другом. CSS Modules решают эту проблему на уровне инструментария: разработчик пишет обычный CSS, а сборщик (Webpack, Vite) преобразует имена классов в уникальные хэши. Это даёт изоляцию без соглашений вроде BEM и без накладных расходов CSS-in-JS в runtime.
Где используется
- React-приложения (Create React App, Vite — поддержка из коробки)
- Vue (аналог —
<style scoped>) - Next.js — встроенная поддержка файлов
*.module.css - Любой проект на Webpack или Vite без желания добавлять CSS-in-JS библиотеку
Как работает CSS Modules
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
}
.button--primary {
background: #0070f3;
color: white;
}
.button--secondary {
background: transparent;
border: 1px solid #0070f3;
color: #0070f3;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ children, variant = 'primary', onClick }) {
return (
<button
className={`${styles.button} ${styles[`button--${variant}`]}`}
onClick={onClick}
>
{children}
</button>
);
}
После сборки класс .button превращается в нечто вроде .Button_button__xK7mQ — уникальное для этого компонента.
Композиция классов
/* utils.module.css */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
/* Card.module.css */
.card {
composes: flex-center from './utils.module.css';
border: 1px solid #eee;
border-radius: 8px;
padding: 16px;
}
Динамические классы (clsx)
import clsx from 'clsx';
import styles from './Button.module.css';
function Button({ children, disabled, variant }) {
return (
<button
className={clsx(styles.button, {
[styles['button--primary']]: variant === 'primary',
[styles['button--disabled']]: disabled,
})}
disabled={disabled}
>
{children}
</button>
);
}
Конфигурация в Vite (по умолчанию)
// vite.config.js — CSS Modules работают без настройки
// любой файл *.module.css обрабатывается как CSS Module
// Опционально настроить генерацию имён:
export default defineConfig({
css: {
modules: {
localsConvention: 'camelCase', // .my-class → styles.myClass
generateScopedName: '[name]__[local]___[hash:base64:5]',
},
},
});
Частые ошибки
- Импортируют без
styles.— пишутclassName="button"вместоclassName={styles.button}, и класс остаётся незахэшированным. - Используют глобальные стили через CSS Modules — для глобального CSS нужен обычный файл без
.module.css, или обёртка:global(.classname). - Не используют
clsxилиclassnames— конкатенация строк для условных классов становится неудобной. - Конфликт с CSS-in-JS — смешивают CSS Modules и Styled Components в одном компоненте без необходимости.