双下划线__是BEM中表示“block__element”直属归属关系的严格语法锚点,不可省略、重复或替换;双中划线--用于绑定修饰符到block或element,形成不可拆分语义单元;单中划线-仅作多词连接符,不表达结构关系。

双下划线 __ 不是分隔符,而是语义归属声明
它明确告诉所有人和工具:这个 element 是那个 block 的直属组成部分,不是后代、不是兄弟、不是独立组件。浏览器不校验它,但 VS Code 插件、stylelint-bem、PostCSS BEM 工具链全靠它识别层级。
常见错误现象:
-
user-card__avatar__img—— 多一个__,工具链直接放弃解析,当成普通字符串 -
user-card_avatar—— 单下划线,语义断裂,avatar变成新块名的一部分,失去“属于 user-card”的约束 -
header__nav__link—— 实际应为nav__link(nav自成 block)或header__link,BEM 不允许元素嵌套
正确写法只有一种结构:block__element,中间严格一个 __,前后都不可省略、不可替换、不可重复。
双中划线 -- 是修饰符的语法锚点,不是视觉风格标记
-- 的作用不是“让名字看起来更长”,而是把修饰符和它所依附的 block 或 element 绑定为一个不可拆分的语义单元。少一个 -、位置偏移、或挂在错误主体上,修饰符就失效。
立即学习“前端免费学习笔记(深入)”;
典型误用:
-
button--disabled .button__label—— 后代选择器破坏 BEM 隔离,应改用button--disabled__label或确保button__label本身响应状态 -
button__icon--loading--small—— 多个--违反单一职责,应拆成两个独立类:button__icon--loading和button__icon--small(仅当二者正交) -
search-form--input——--不能挂到 block 上表示 element,这是对 BEM 结构的根本误解;正确应为search-form__input
修饰符永远依附于某个已定义的 block 或 element,不存在“独立 modifier”。
中划线 - 是纯单词连接符,和层级/状态无关
- 唯一合法用途就是拼多词:在 block 名里(如 user-profile)、在 element 名里(如 user-profile__bio-section)、在 modifier 名里(如 --is-hidden)。它不表达任何结构关系,混进 __ 或 -- 位置就会导致语义错位。
容易踩的坑:
-
search-form_input—— 应为search-form__input,_不是 BEM 合法符号 -
menu-item--active——menu-item是 block,但item实际是它的 element,正确应为menu__item--active -
CardTitle或cardTitle—— BEM 要求全小写 + 中划线,大小写混用会让团队 grep 失效、工具链无法匹配
所有类名必须扁平、全小写、只含字母、数字、-、__、--,且三者位置和数量有唯一合法组合。
动态拼接类名时,__ 和 -- 最容易被 runtime 破坏
Vue/React 模板中手动拼 ${block}__${el} 看似方便,但一旦 el 来自 props 或用户输入,就可能含空格、大写、特殊字符或意外的 -,结果生成 user-card__Avatar 或 user-card__bio section 这类非法名。
安全做法:
- 用工具函数做标准化处理,比如
kebabCase(el)清洗元素名 - 禁止直接插值 modifier,
--${state}必须提前白名单校验(如只允许disabled、hovered) - 服务端渲染或构建时静态检查类名合法性,比 runtime 报错更早拦截问题
BEM 的深层含义不在“怎么写好看”,而在于用符号强制表达设计契约:谁属于谁、什么状态依附于什么实体、哪些命名是可预测可工具化的。写错一个 __ 或 --,表面看样式照常生效,实则协作链路已经断开。


















