bem是让响应式样式真正可维护的唯一轻量解法,它将响应式逻辑收敛到修饰符层,按变化范围严格区分block(整体结构变更)与element(自身微调)修饰符,禁用跨block类和is-类混用,确保媒体查询绑定单bem类、语义明确、可校验。

BEM不是为响应式而生的,但它是让响应式样式真正可维护的唯一轻量解法。 没有BEM,@media 规则会散落在各处、修饰符语义模糊、组件一挪就失效;有了BEM,响应式逻辑可以全部收敛到修饰符层,比如 product-card--mobile 或 header__nav--collapsed,而不是靠 .header .nav ul li a 这种脆弱选择器硬撑。
响应式类名该写在 Block 还是 Element 上
取决于变化范围。整块布局切换(如卡片从网格变列表),用 Block 修饰符:product-list--stacked;仅内部元素行为变化(如按钮图标位置调整),用 Element 修饰符:button__icon--right。错误做法是把响应式逻辑塞进通用类名里,比如 text-sm 或 hidden-md——这类类名脱离上下文就失效,且无法被 stylelint 校验。
常见错误现象:card__content 在桌面端显示三列,移动端却靠 JS 动态加 is-mobile 类控制,结果这个 is-mobile 在其他组件里也被滥用,最终变成全局状态开关。
- Block 修饰符用于整体结构变更(栅格、折叠、方向)
- Element 修饰符仅限该元素自身视觉/排版微调(对齐、间距、可见性)
- 禁用跨 Block 的响应式类,如
hidden-on-mobile—— 它破坏了作用域边界
如何用 BEM 写出可预测的媒体查询
关键不是怎么写 @media,而是怎么组织它。所有响应式规则必须绑定到明确的 BEM 类,且只允许单类名触发,禁止嵌套选择器。例如:
.product-card--mobile {
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
.product-card--mobile {
display: grid;
}
}
这样写的好处是:调试时直接搜 product-card--mobile 就能定位全部响应逻辑;CI 能用 stylelint-selector-bem-pattern 确保没人偷偷写 .product-card .price 配合媒体查询。
- 每个
@media块内只能出现一个 BEM 类选择器 - 不许用
&在 Sass 中生成响应式嵌套,如.card { @media { &__title { ... } } }—— 编译后仍可能违反单类名原则 - 修饰符名要表达意图,而非设备,
--responsive-grid✅,--on-tablet❌
为什么 is- 类和 BEM 修饰符不能混用
is- 是状态类(如 is-open),描述运行时变化;BEM 修饰符(如 --expanded)描述设计契约中的变体。两者语义冲突:一个由 JS 控制、生命周期短;一个由模板决定、稳定可推导。混用会导致 stylelint 无法校验、开发者分不清哪个该写进 HTML,哪个该由 JS 切换。
常见错误现象:提交代码含 <div class="modal is-visible header--sticky">,CI 不报错,但后续有人删掉 <code>is-visible 却忘了同步删 header--sticky,导致样式残留。
-
is-类必须由 JS 动态增删,不能出现在初始 HTML 中 - BEM 修饰符必须由模板或构建时确定,静态存在
- 响应式场景下,优先用 BEM 修饰符;仅交互反馈类(如
is-loading)才用is-
最易被忽略的一点:BEM 的响应式能力不来自语法本身,而来自对“谁该负责响应”的严格划分。一旦允许 __element 跨 Block 复用,或让修饰符承担布局职责(比如 --full-width),整个响应逻辑就退化成旧式 CSS 的翻版——看着模块化,实则耦合更深。











