ID选择器#header权重为1,0,0,远超BEM类名0,1,0,导致.button--primary等修饰符无法覆盖;ID仅可用于JS锚点或语义标识,样式必须由BEM类名驱动,禁用其参与CSS匹配。

为什么#header会让.button--primary失效
不是顺序问题,是浏览器按数字硬算权重:#header 是 1,0,0,而 .button--primary 是 0,1,0。哪怕你把它写在后面,也压不过。更麻烦的是,#header .button--primary 直接跳到 1,1,0,后续所有修饰符(比如 .button--primary--loading)都得靠 !important 或再套 ID 才能覆盖。
BEM里ID到底能不能用
ID 可以存在,但绝不能参与 CSS 匹配。它只该干两件事:JS 锚点定位(document.getElementById('modal')),或语义标识(如 aria-labelledby="header-title")。样式必须由 BEM 类名驱动,比如把 <div id="main-content"> 改成 <div id="main-content" class="layout__main">,CSS 里只认 .layout__main。
哪些写法看似安全实则偷偷用了ID
这些不是语法错误,但会悄悄破坏 BEM 权重平衡:
-
#app .user-card__avatar—— 权重直接拉高,后续无法用.user-card__avatar--large安全覆盖 -
div.card__title—— 标签 + 类 = 0,1,1,高于标准 BEM 的 0,1,0 -
.card .card__title—— 两个类叠加,权重 0,2,0,违背 BEM “单类选择器”原则 - Vue/React 组件循环渲染时写死
id="modal"—— JS 只能取到第一个,逻辑错乱
怎么落地不踩坑
真正难的不是记住规则,而是敢不敢删掉第一行含 # 的 CSS:
立即学习“前端免费学习笔记(深入)”;
- JS 定位优先用
document.querySelector('.user-card__delete'),类名足够唯一 - 重构旧项目时,先确保所有 CSS 已脱离 ID,再删 HTML 中的
id属性,否则 JS 功能会断 - CI 阶段用
stylelint-selector-bem-pattern插件,配置allowedSelectors: ["class"],直接拦截含#的规则 - 需要限定作用域时,改用属性选择器:
[data-page="product"] .product-card__price,权重仍是 0,1,0


















