bem是ui库css作用域的唯一可行解,因其通过ui-button__icon--loading类名静态承载归属、角色、状态三重信息,天然适配cdn/ssr等无构建场景,强制单类名选择器避免样式污染,且须配合目录对齐、ci拦截与工具链落地。

为什么BEM是UI库CSS作用域的唯一可行解
Browsers don’t have CSS modules — 你发布的 .button 就是全局的,哪怕用了 CSS Modules 或 scoped styles,最终生成的类名(如 Button_root__abc123)只在构建时有效,而开源库必须支持 unpkg / CDN / SSR 直出等无构建场景。此时 ui-button__icon--loading 这种静态、语义完整、自带归属信息的类名,就是唯一可信赖的契约。
- 没有 BEM,
.icon可能被宿主项目的.modal .icon覆盖,或被reset.css的img.icon { margin: 0 }意外重置 - BEM 类名天然携带三重信息:
ui-button(谁的)、__icon(什么角色)、--loading(什么状态),缺一不可 - 第三方开发者看
ui-tooltip__arrow--top就知道它属于 tooltip、是箭头、当前朝上,不需要翻源码猜意图
为什么修饰符不能叠加(button--primary--large--disabled 是错的)
这种写法看似省事,实则直接破坏 UI 库的可组合性与主题接管能力。BEM 修饰符设计初衷是「布尔开关」,每个都应能独立存在、单独覆盖、被主题系统按粒度注入。
-
button--primary button--large button--disabled✅:可单独移除button--large,也可被theme-dark扩展为button--primary theme-dark__button -
button--primary--large--disabled❌:无法复用--large,无法被主题系统识别,CI 必须用stylelint-selector-bem-pattern拦截 - 更隐蔽的问题:它暗示“大号主要按钮”是一个原子变体,但实际业务中,“大号”可能出现在所有按钮类型上——违背单一职责
为什么 SCSS 嵌套里写 & a 就等于放弃 BEM
BEM 的约束力不在名字,在选择器行为。SCSS 中 .ui-button { & a { ... } } 编译后是 .ui-button a,这是一个带空格的嵌套选择器,完全绕过 BEM 的单类名隔离原则。
- 它会污染所有
a标签,哪怕宿主项目在header或footer里也用a,样式都会被意外接管 - 正确写法只有两种:
&__icon(生成.ui-button__icon)或&--loading(生成.ui-button--loading) - CI 阶段必须启用
postcss-bem-linter或stylelint-selector-bem-pattern,对& div、& > span、& a等无语义嵌套报错
为什么目录结构和类名必须严格对齐
一个 ui-button.css 文件里出现 .ui-input__field,说明这个库的模块边界已经失效——BEM 不是命名游戏,它是“文件即模块”的物理契约。
- 每个 block 对应一个目录:
/components/ui-button/,里面只放ui-button.css、UiButton.tsx、测试等 - 不允许
ui-button__icon出现在/components/icon/下;也不允许ui-button.css里定义.ui-tooltip__trigger - CI 检查必须校验:类名前缀(
ui-button)是否与文件路径、组件名、导出名完全一致,不一致即阻断发布
block__element--modifier,而是让团队在加一个新交互态时,本能地新增 ui-button--hovered 而不是往 JS 里塞 className="button hovered";是在重构时,敢直接全局搜索 ui-dialog__close 定位全部调用,而不担心误伤其他模块。这背后全是目录、工具链、CI 和最初几个模块的示范性实现共同焊死的。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











