tab和tab-panel必须是独立Block,因二者语义解耦、可分别复用于导航、弹窗、accordion等场景,且tab-panel可能脱离tabs独立渲染;tabs仅管控tab列表布局与间距,不涉及面板内容或状态逻辑。

标签页组件的 BEM 层级不是按 DOM 深度来嵌套,而是按「可独立复用的功能单元」来切分——tabs 是 Block,tab 和 tab-panel 必须是同级 Block,不能写成 tabs__tab__panel 或 tabs__panel。
为什么 tab 和 tab-panel 必须是独立 Block?
它们在语义、复用性、DOM 位置上完全解耦:
-
tab负责切换入口(按钮/链接),可单独用于顶部导航、侧边栏或弹窗内 -
tab-panel负责内容容器,可能被accordion、stepper复用,甚至脱离 tabs 独立渲染(如 SSR 预加载) - 若强行塞进
tabs__panel,会导致 CSS 规则强绑定 DOM 结构:一旦tab-panel被移到tabs外层(比如 modal body 中),样式立即失效 - JS 控制显隐时,
tab-panel--active的状态逻辑和tabs无关,它该响应的是全局状态或路由参数,而非父级 class
tabs Block 内部只管什么?
tabs 的职责非常窄:仅定义 tab 列表的布局、对齐、间距、滚动行为等结构规则,不碰颜色、字体、面板内容。它的子元素只能是 tabs__list 和 tabs__item,且必须平铺书写:
.tabs { display: flex; }
.tabs__list { overflow-x: auto; scroll-behavior: smooth; }
.tabs__item { flex-shrink: 0; }
.tabs__item--active { border-bottom: 2px solid var(--color-primary); }
-
tabs__item里只放<a>或<button>,不包裹tab组件实例 - 禁止出现
.tabs__item .tab这类后代选择器——它隐含“tabs 下必须用 tab 组件”的假设,破坏可替换性 - 如果设计稿要求 tab 文字加图标,正确写法是
tabs__item__icon(Element),不是tab__icon(那是另一个 Block)
如何避免 tab 和 tab-panel 的 class 名冲突?
冲突根源常来自命名模糊或修饰符越界,关键约束有三条:
立即学习“前端免费学习笔记(深入)”;
- Block 名必须带业务语义:
user-tabs、doc-tabs合理;tabs作为通用 Block 可接受,但项目中首次使用必须配_tabs.scss文件隔离,不可与tab混用 -
tab的 Modifier 只描述切换行为:tab--disabled、tab--loading,禁用tab--small(尺寸应由tabs的tabs--compact统一控制) -
tab-panel的 Modifier 只表达内容状态:tab-panel--empty、tab-panel--loading,绝不出现tab-panel--active——激活态由 JS 切换is-active这类通用状态类,或交由框架(如 React 的aria-hidden)管理
第三方 Tabs 库(如 Ant Design)能套 BEM 吗?
不能硬套。例如 Ant 的 ant-tabs-nav 不应改写成 my-tabs__nav:
- 它不是你项目的 Block,强行重命名会阻断官方升级路径,且破坏其内部 JS 逻辑依赖的选择器
- 正确做法是用 wrapper 容器套一层
my-tabs,只在该容器上声明变量或覆盖皮肤层:.my-tabs { --tabs-nav-bg: #f5f5f5; } - 若需深度定制,优先通过库提供的
classNameprop 注入my-tabs__item,而非修改其原始 class - 所有自定义逻辑(如动画、键盘导航)应封装在
my-tab-controller这类新 Block 中,与 UI 库解耦
最易被忽略的一点是:BEM 层级不是靠缩进或文件夹深度体现的,而是靠 class 名是否能在不看 HTML 的前提下,让人准确说出它的作用域边界。一个 tab-panel 类名,必须让人立刻意识到——它不归 tabs 管,也不该被 tabs 的 CSS 规则影响。


















