Tab结构必须用语义化标签组合,正确结构是role="tablist"包裹role="tab"按钮和role="tabpanel"内容区,并通过aria-controls与aria-labelledby双向关联;实操需设tabindex、aria-selected、hidden属性,用CSS控制显隐,支持键盘导航,框架中优先用条件渲染管理状态。

Tab结构必须用语义化标签组合,不能只靠div堆砌
浏览器原生不识别div作为可切换面板,屏幕阅读器和键盘导航会直接失效。正确结构是:role="tablist"容器包裹role="tab"按钮,再用role="tabpanel"对应内容区,且必须通过aria-controls和aria-labelledby双向关联。
常见错误:用ul/li模拟tab但没加role属性,或把所有面板都设为display: block再靠JS隐藏——这会让不可见内容仍被读屏器朗读。
实操建议:
-
tab元素需有tabindex="0",激活态加aria-selected="true" -
tabpanel默认设hidden属性,激活时移除 - 每个
tab的id要和对应tabpanel的aria-labelledby值一致
用CSS控制显隐比JS操作display更可靠
直接改style.display容易和外部CSS冲突,尤其当面板内有position: absolute子元素时,display: none会导致尺寸计算异常。用hidden属性配合[hidden] { display: none; }更稳妥,它优先级低、易覆盖,且支持CSS过渡动画。
立即学习“前端免费学习笔记(深入)”;
性能影响:visibility: hidden保留布局空间,display: none触发重排,hidden属性本质等同于后者但语义明确。
实操建议:
- 在CSS中统一定义
[hidden] { display: none; },不要写display: none !important - 需要淡入效果时,对
tabpanel加opacity和transition,但必须配合hidden属性开关——否则未激活面板仍参与渲染 - 避免用
max-height: 0做折叠动画,内容高度不确定时会失真
键盘交互必须支持Tab/Shift+Tab+Arrow键切换
仅鼠标点击无法满足无障碍要求。用户按Tab进入tab区域后,应能用左右箭头切换焦点,Enter或Space激活当前tab,Home/End跳转首尾。
容易踩的坑:监听keydown时没阻止默认行为,导致按ArrowRight时页面横向滚动;或没处理循环切换(到末尾后按右箭头跳回开头)。
实操建议:
- 给
tablist容器设tabindex="0",确保可聚焦 - 箭头键切换时,用
focus()主动移动焦点,而非仅改aria-selected - 按
Enter或Space时调用click()事件,复用已有的点击逻辑,避免重复实现
React/Vue中别手动管理tab状态,优先用key或条件渲染
在框架里硬写document.getElementById查节点、手动切hidden属性,既破坏响应式逻辑,又难测试。框架的条件渲染天然隔离状态,比DOM操作更可靠。
Vue示例:v-if="activeTab === 'panel1'"比:class="{ hidden: activeTab !== 'panel1' }"更彻底——前者卸载组件,后者只是隐藏DOM。
React示例:用key强制重置子组件状态,比如<TabPanel key={activeTab} />,避免切换后表单残留旧数据。
实操建议:
- 框架项目中,tab索引应作为state管理,而非依赖DOM属性
- 避免在
useEffect或mounted里查询tablist节点并绑定事件——用事件委托或框架原生事件 - 服务端渲染时,确保首屏激活的tab对应内容已渲染,否则出现“闪动”
aria-labelledby,屏幕阅读器就无法把tab和面板关联起来;漏掉focus()调用,键盘用户就会卡在第一个tab上。这些细节没有报错,但会让一部分人完全无法使用。



















