bem修饰符必须用双连字符--命名,如.btn--disabled;状态切换须由js控制class增删;多修饰符按“基础→状态→变体”排序;ssr需服务端与客户端状态严格对齐。

修饰符命名必须带连字符,否则BEM语义失效
很多人写 .btn--disabled 时漏掉双连字符,写成 .btn-disabled 或 .btn_disabled,这会让 BEM 的“修饰符”意图完全丢失——它不再是状态标记,而退化成另一个普通类名。CSS 里看不出它是临时状态还是固有变体,团队协作时尤其容易误改。
正确做法只有一种:-- 是修饰符的强制分隔符,和块名、元素名严格区隔。比如按钮禁用态是 .btn--disabled,加载中是 .btn--loading,成功态是 .btn--success。
- 错误:
.btn-disabled(被当成独立组件或元素) - 错误:
.btn_disabled(下划线在 BEM 中不表修饰符) - 正确:
.btn--disabled(明确表达“btn 的 disabled 状态”)
状态切换必须靠 JS 控制 class 切换,不能依赖 :hover/:focus 伪类
BEM 修饰符管的是「可编程的状态」,比如 API 加载中、表单校验失败、权限不足等需要逻辑判断的场景。纯交互反馈(如鼠标悬停)该用伪类,混在一起会模糊职责边界,也让状态难以被测试或调试。
例如,一个提交按钮的三种状态:.btn--idle(初始)、.btn--submitting(JS 触发)、.btn--error(接口返回后设置),都得由 JS 显式增删 class 控制。
- 别把
.btn--hover当真——浏览器原生:hover更轻量、更可靠 - 禁用态
.btn--disabled必须同步设disabled属性,否则键盘用户仍可聚焦 - 避免用
!important强行覆盖伪类样式,那说明修饰符层级或选择器设计有问题
多个修饰符共存时,顺序不重要,但 class 列表要可读
.btn--primary.btn--disabled 和 .btn--disabled.btn--primary 渲染效果完全一样,CSS 不关心 class 顺序。但人要读,所以建议按「基础 → 状态 → 变体」排列,比如先定类型(--primary),再定状态(--disabled),最后定尺寸(--small)。
这样写不仅顺眼,还能快速识别出哪个是当前主导状态。如果项目里有人写 .btn--disabled--primary(非法嵌套修饰符),直接报错,BEM 不允许修饰符叠加成新修饰符。
- 合法:
<button class="btn btn--primary btn--disabled"></button> - 非法:
<button class="btn btn--primary-disabled"></button>(这不是修饰符,是新块) - 非法:
<button class="btn btn--primary btn--disabled btn--large"></button>(三个修饰符没问题,但--large若只影响尺寸,应归为--size-large更清晰)
服务端渲染或 SSR 场景下,修饰符 class 必须和服务端状态严格对齐
React/Vue 服务端渲染时,如果 HTML 初始就带 .btn--success,但客户端 JS 没立刻同步状态,会出现闪屏或样式错乱。更隐蔽的问题是:服务端判定了「已登录」加了 .nav--logged-in,但客户端初始化时没读取同构状态,导致 class 被清空或重复添加。
关键不是“怎么加 class”,而是“谁负责判定状态”。BEM 修饰符只是 CSS 层的投影,背后必须有统一的状态源(比如 Redux store、Pinia state 或 SSR 注入的全局变量)。
- Next.js 中用
getServerSideProps注入初始状态,并在组件中用useEffect同步更新 class - Nuxt 3 推荐用
useState+onMounted避免服务端/客户端差异 - 切忌在模板里写
v-if="user.isLoggedIn" class="nav--logged-in"—— 这样服务端没 user 对象时会漏 class
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











