工具类只能加在BEM元素层级,如card__content u-padding-md;禁止加在块根节点(如card u-margin-top-lg)或混用视觉修饰(如u-bg-gray-100),须单属性、无条件、零特异性,且CSS加载顺序必须工具类优先于BEM组件。

工具类必须只加在BEM元素层级,不能侵入块级结构
工具类(如u-padding-md、u-text-center)的合法位置只有一个:block__element层级。写在block根节点上(比如<div class="card u-margin-top-lg">)会破坏组件封装性,让外部样式直接干预内部布局契约。
<p>常见错误现象:卡片组件<code>card自己定义了padding,结果被u-padding-xl覆盖,导致所有使用该卡片的地方排版错乱;更糟的是,这个u-padding-xl可能来自另一个不相关的模块,调试时根本找不到源头。
-
✅ 允许:<div class="card__content u-padding-md"> —— <code>card__content本身不设padding,仅靠工具类提供通用排版能力 -
❌ 禁止:<div class="card u-bg-gray-100"> —— 颜色属于视觉契约,应由<code>card--gray这类修饰符控制,而非工具类 -
⚠️ 危险:<div class="card__header u-flex u-items-center"> —— 若<code>card__header已有display: flex,工具类就多余;若没有,说明结构职责不清OOCSS工具类必须无上下文、零特异性、单属性
一个工具类如果带
:hover、@media、或同时设置padding和border,它就不是真正的工具类,而是伪装成工具的业务样式碎片。这类类名一旦复用,必然引发权重冲突或行为错位。性能影响:带伪类或媒体查询的工具类无法被CSS引擎快速匹配,且会阻止浏览器对同名类做合并优化;构建时还可能被PurgeCSS误判为“未使用”而删掉。
立即学习“前端免费学习笔记(深入)”;
-
✅ 正确:.u-text-bold { font-weight: 700; }—— 单属性、无条件、无嵌套 -
❌ 错误:.u-text-bold:hover { font-weight: 800; }—— 伪类绑定破坏复用前提 -
❌ 错误:.u-bg-primary { background-color: #007bff; padding: 8px; border-radius: 4px; }—— 混合职责,违反单一性
BEM与工具类共存时的加载顺序不能颠倒
CSS文件里,工具类规则必须出现在所有BEM组件规则之前。否则
.btn--primary这种多级选择器(哪怕只是单类名)会因特异性更高而覆盖.u-padding-md,导致工具类失效——这不是bug,是层叠逻辑的自然结果。最容易被忽略的一点:Webpack/Vite的
css-loader默认按import顺序拼接CSS,但如果你用@import或PostCSS插件动态注入,顺序就不可控。DevTools里看到u-padding-md被划掉,90%是加载顺序错了,而不是类名写错。- 确保
utilities.css在components/card/card.module.css之前引入 - 禁用
cssnano的mergeLonghand选项,否则u-padding-md和u-padding-lg可能被压缩成一个声明,丢失语义 - 不要给工具类加
!important——它会让BEM组件调试变成黑盒,连devtools的“Computed”面板都看不出覆盖路径
动态添加工具类时,safelist正则必须显式覆盖
JS运行时给
card__body加上u-overflow-hidden,PurgeCSS默认不会扫描这段代码,结果构建后该类名消失,滚动截断失效。这不是工具类的问题,是构建链路没兜住动态路径。正确做法不是关掉PurgeCSS,而是用正则精准放行:
purge: { safelist: [ /u-(padding|margin|text|bg)-.+/, // 允许所有u-前缀工具类 /card__content/ // 同时保留BEM元素,避免误删 ] }注意:
/u-.+/太宽泛,可能放过未定义的u-fake-class;/^u-/又太窄,漏掉u-sm:flex这类响应式变体。 -


















