必须用 role="tablist" 作为外层容器并配合 button[role="tab"],因屏幕阅读器只识别 ARIA 角色;需同步更新 aria-selected 与 hidden,且切换后必须手动调用 focus() 确保键盘焦点与语义一致。

原生 HTML 实现多面板 Tab 切换,必须用 ARIA 语义结构,不能靠 CSS 伪类或 frameset 硬切——否则键盘不可操作、读屏器无法识别、SEO 和 SSR 都会出问题。
为什么用 role="tablist" 而不是 ul 或 div 包裹 tab 按钮
屏幕阅读器只认 ARIA 角色,不认 CSS 类名或 DOM 层级。用 ul 或无角色 div 做 tab 容器,读屏器会把它当普通列表或区块,完全跳过“这是可切换的标签页”这一语义。
实操建议:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
role="tablist"是强制要求,必须作为最外层容器;它会告诉辅助技术:“这里面是一组可导航的 tab” - 每个按钮必须是
<button role="tab">,不能用<a>(会触发跳转)或<div>(无键盘交互能力) - 漏掉
role="tablist"是导致“键盘按 Tab 键直接跳过整个 tab 区域”的最常见原因
aria-selected 和 hidden 必须同步更新,不能只改 class
很多项目用 JS 切换 .active 类控制显隐,但 class 对屏幕阅读器无效。如果只加 display: none,读屏器仍可能朗读隐藏内容;如果只设 aria-selected="true" 但没给对应面板加 hidden,焦点和语义就脱节了。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- 激活按钮:设
aria-selected="true",对应面板移除hidden属性,并确保tabindex="0" - 非激活按钮:设
aria-selected="false",对应面板加上hidden(不是aria-hidden="true") - 别用
visibility: hidden或opacity: 0隐藏面板——它们不阻止键盘聚焦,也不影响读屏器朗读 - 切换时,先更新所有按钮的
aria-selected,再统一处理面板的hidden,顺序错会导致短暂状态不一致
键盘焦点没跟着 tab 切换走?是因为没手动调用 .focus()
点击第二个 tab 按钮后,内容变了,但按 Tab 键仍停在第一个按钮上——这不是浏览器 bug,是 JS 没接管焦点迁移逻辑。ARIA Tab 规范明确要求:用户激活某 tab 后,焦点必须落在该按钮上;若面板内有可聚焦元素(如输入框),焦点还应进一步落到面板首个有效子元素。
实操建议:
- 每次点击后,立即对目标按钮执行
button.focus() - 如果面板内容可交互,切换完再执行
panel.querySelector('input, button, [href], [tabindex]')?.focus() - 别依赖
autofocus属性——它只在页面首次加载时生效,切换时不触发 - 监听
keydown处理方向键:对ArrowRight/ArrowLeft计算索引并调用.focus(),Home/End 同理
响应式下 tab 排列方向变化时,aria-orientation 必须动态更新
横向 tab 在桌面端正常,小屏折叠成竖排或下拉菜单后,role="tablist" 的默认 aria-orientation="horizontal" 就失效了。此时方向键逻辑仍按左右播报,但视觉上是上下排列,造成认知错位。
实操建议:
- 检测视口宽度,竖排时显式设置
aria-orientation="vertical" - 下拉菜单场景不应再用
role="tablist",而应切换为role="combobox"或role="menu",并改用Enter/Escape行为 - 不要在 CSS 里用
@media改变role值——role 是语义属性,必须由 JS 动态写入 DOM
真正难的不是让 tab “看起来能切换”,而是让每一次点击、Tab 键移动、屏幕朗读都指向同一套状态。最容易被忽略的,是切换后焦点落点与面板可访问性之间的衔接——它不在 HTML 结构里,也不在 CSS 动画中,而在那行没写的 .focus() 里。


















