文件名必须带.module.scss后缀,webpack(含cra、vite、next.js)仅对此类文件启用css modules逻辑;写成button.scss则样式全局注入,污染页面。

文件名必须带 .module.scss 后缀
Webpack(包括 CRA、Vite、Next.js)只对 .module.scss 文件启用 CSS Modules 逻辑。写成 Button.scss 或 Button.css,哪怕内容完全一样,也会被当作全局样式注入 ,直接污染整个页面。
常见错误:
-
import './Button.scss'→ 全局生效,.btn类名裸奔 -
import styles from './Button.module.css'→ 正确,但用了.css,失去 SCSS 嵌套/变量能力 -
import styles from './Button.module.scss'→ ✅ 推荐,兼顾模块化 + SCSS 特性
className 必须通过 styles.xxx 访问
CSS Modules 会把 .container 编译成类似 container__abc123 的哈希名,这个映射只存在于 JS 对象中。直接写字符串类名,DOM 上就是原始名,浏览器找不到对应规则。
正确写法示例:
import styles from './Dashboard.module.scss';
<p>function Dashboard() {
return </p><div classname="{styles.container}">...</div>;
}
容易踩的坑:
-
className="container"→ 失效,不匹配哈希类 -
import * as styles from './Dashboard.module.scss'→ 错误导入方式,styles是默认导出对象,不是命名空间 -
className={styles['container-' + size]}→ 动态键名在编译期不存在,结果是undefined
SCSS 嵌套和变量仍可用,但作用域只限最外层
SCSS 的 .container { .item { } } 嵌套语法照常工作,但 CSS Modules 只会对最外层选择器(.container)加哈希;内部的 .item 不单独哈希,而是依赖父级生成的类名,最终输出类似 container__abc123 .item。
这意味着:
- 你可以用
$primary-color: #007bff和@mixin flex-center,只要构建链有sass-loader - 想复用其他模块的样式?不行。
styles对象只包含当前文件定义的类,跨文件引用需显式导入另一个xxx.module.scss - 需要同时用局部类和全局动画类?拼接字符串安全:
className={`${styles.card} animate fadeIn`},但确保animate已全局引入
伪类、响应式、主题切换要靠 CSS 变量驱动
单纯靠 styles.button + styles['button--large'] 难以覆盖 :hover、@media 或暗色模式逻辑。更可靠的做法是在 .module.scss 中定义私有 CSS 变量,并用 var(--_hover-bg, #f0f0f0) 调用:
.button {
--_bg: var(--button-bg, #007bff);
--_hover-bg: var(--button-hover-bg, #0056b3);
background: var(--_bg);
&:hover { background: var(--_hover-bg); }
}
这样,上层组件可通过 style={{ '--button-bg': '#ff6b6b' }} 或根节点设置 :root { --button-bg: ... } 安全定制,无需破坏模块封装。
真正难处理的是:变量作用域不自动继承到子组件,style 属性也不能穿透 React.cloneElement —— 这部分得靠设计约定或 Context 传递,不是靠文件后缀能解决的。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











