主题类名须从CSS Modules导入的styles对象取值,不可硬编码;每个主题应独立文件、运行时按需加载;主题变量必须定义在:root或html[data-theme]中并确保全覆盖,且var()需带fallback。

主题类名必须从模块对象取值,不能硬编码
CSS Modules 会对类名自动哈希(如 theme-dark__abc123),直接在 JSX 中写 className="theme-dark" 会导致样式丢失。所有主题类都得定义在 .module.css 文件里,再通过 import styles from './Button.module.css' 后用 styles['theme-dark'] 或 styles.themeDark 访问。
常见错误:
- 把
.theme-dark写成全局类,混入:global——破坏模块隔离,主题一换全站“染色” - 命名含短横线(
theme-dark)却用点语法访问(styles.theme-dark)——JS 报错,返回undefined - 在
clsx中拼接时漏掉模块导出的 key,比如传入字符串props.theme却没校验它是否对应styles中的真实键名
别用单个 CSS Modules 文件塞所有主题
把 light/dark/green 全塞进一个 themes.module.css 里,看似省事,实则让变量作用域失控、热更新失效、构建产物臃肿。真正可控的方式是每个主题一个独立文件:theme-light.module.css、theme-dark.module.css。
运行时按需加载:
立即学习“前端免费学习笔记(深入)”;
- Webpack:支持
require(`./themes/${theme}.module.css`) - Vite:必须用异步
import(),否则 HMR 不触发重载 - 注意:动态 import 的路径必须是静态可分析的(不能是
import(`./themes/${Math.random()}.css`))
开发时如果改了主题文件但样式没更新,大概率是构建工具对动态路径的热更新支持不完整,临时解法是顺手改一下被 import 的那个 CSS 文件本身。
CSS Modules 只管结构,变量定义必须抽离到 :root
CSS Modules 负责组件结构类名(如 button__primary),不负责颜色、间距等主题变量。所有变量(--color-primary、--bg-surface)必须统一定义在 :root 或 html[data-theme="dark"] 下,由 JS 控制 document.documentElement.setAttribute('data-theme', 'dark') 触发切换。
这样做的好处:
- 组件样式只需写
background-color: var(--bg-surface);,无需重复声明主题变体 - 避免 BEM 修饰符语义错位(比如
button--dark实际不是按钮状态,而是系统级外观) - PurgeCSS 不会误删未显式出现在 HTML 中的类名(因为根本不用写
button--dark)
关键细节:所有 var() 必须带 fallback,例如 color: var(--text-default, #333);,否则 JS 加载失败或 SSR 首屏时变量未定义,文字直接变透明。
data-theme 切换后,变量覆盖范围容易漏掉
很多人写了 html[data-theme="dark"] { --text-color: #fff; },却忘了同步覆盖 --card-bg、--border-color、--shadow-sm 等,导致部分区域仍是亮色。这不是写法错,是变量覆盖不全。
检查方法:
- 打开 DevTools → Elements → 选中
html元素 → 查看 Computed 面板,搜--,确认所有主题变量在当前data-theme下都有值 - 深色模式块必须放在 light 主题定义之后,否则层叠顺序导致覆盖失败
- 不要把变量定义在局部选择器里(如
.card { --bg: #fff; }),子元素无法继承,必须放:root或html[data-theme]
最易忽略的一点:变量回退值(fallback)和作用域是两回事。即使写了 var(--color, #000),若该变量根本没在当前作用域定义,浏览器仍会按未定义处理——所以必须确保每个变量都在对应 data-theme 块中显式重定义。


















