bem类名被覆盖是因为它仅是命名约定,不提供作用域隔离;两个组件输出同名类到同一css文件时,后定义规则会覆盖前者,浏览器只按类名、权重和加载顺序生效。

为什么BEM类名写了还是被覆盖?
因为BEM只是命名约定,不是作用域隔离。两个组件都输出 .card__header 到同一份 CSS 文件里,后定义的规则就会覆盖前一个——和你写没写双下划线没关系。浏览器只看类名字符串是否一致、权重是否更高、加载顺序是否靠后。
常见现象:.user-list__item 设了 float: left,但另一个模块的同名类把父容器 .user-list 的 height 清空了,整个列表塌成一条线;或者 .header__logo 在 A 页面是 24px,在 B 页面被另一处同名规则改成 16px,全站生效。
- BEM 类名仍全局可见,构建时未哈希 = 白写
- 团队不敢删旧类,因为不知道哪个页面还依赖它
-
document.querySelector('.form__input')依然能拿到并改样式,暴露面大
CSS Modules 是唯一能切断塌陷链路的方案
它让每个组件的类名在构建时生成唯一哈希,比如 .card__body 编译成 Card_card__Kx2f1,彻底脱离全局符号体系。只要不用硬写 class="card__body",就不会被其他模块的同名规则干扰。
- Vite 中需配
css.modules: { generateScopedName: '[name]__[local]___[hash:base64:5]' },否则[local]模式下类名仍裸奔 - Webpack 用户要确保
css-loader启用了modules: true,且文件后缀为.module.css - Vue 的
<style scoped></style>不等于<style module></style>:前者加属性选择器(如.card__body[data-v-abc]),但同名类仍在 DOM 上,仍可能被外部 CSS 通过该属性选择器覆盖 - 要覆盖第三方组件(如 antd),必须显式写
::global(.ant-card) { overflow: visible; }
没构建环境时怎么兜底?用外层命名空间物理隔离
纯 HTML 或无法改构建流程时,给根容器加唯一属性,所有样式前置该属性,比堆叠选择器更可控也更可维护。
- 给应用挂载点加
id或data-scope:<div id="sales-app"> 或 <code><div data-scope="admin"> <li>所有自定义规则前置该标识:<code>#sales-app .card__body { padding: 16px; },避免和#admin-app .card__body冲突 - Tailwind 用户可在
tailwind.config.js中设prefix: 'tw-',让text-lg变成tw-text-lg,避开 Bootstrap 同名类 - 禁止用
button.ant-btn这类标签+类组合——很多库用[class*="ant-btn"]或 JS 动态加类,button标签可能根本不存在 -
!important不改变 specificity 四元组值,它只是跳过层叠顺序;如果两个都带!important,还得回退到比四元组 - 手拼字符串易漏空格、错连字符、大小写混用,本地跑得通,CI 上直接挂
- SCSS 嵌套出
.card { .title { } }→ 编译出.card .title,破坏 BEM 封装性,且无法从类名反推来源 - modifier 不能脱离 block 单独使用:
button--primary错,checkout-button--primary对
哪些操作看似解急实则埋雷?
盲目加权重、滥用 !important、手拼 className 字符串,短期看着有效,长期会让后续修改变成“谁最后加 !important 谁赢”的混乱局面。











