应使用 data-tab 与 id 关联按钮和面板,避免依赖 DOM 顺序;统一用 role="tablist"/"tab"/"tabpanel"、aria-controls/aria-labelledby/aria-selected 等属性保障无障碍;首次加载需手动激活默认项,切换前先隐藏所有面板再显示目标项,并处理 hidden 导致的焦点丢失问题。

用 data-tab 关联按钮和面板,别靠 DOM 顺序硬匹配
常见错误是写 btn.nextElementSibling 或 btn.parentNode.nextElementSibling 找内容区——结构一改就全崩。正确做法是让每个按钮带 data-tab="products",对应面板加 id="panel-products",用 document.querySelector(`#panel-${tab}`) 精准定位。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 按钮统一用
<button type="button"></button>,避免意外提交表单 - 面板统一加
role="tabpanel",便于屏幕阅读器识别 - 首次加载时必须手动激活一个,默认选中第一个按钮并显示其对应面板,否则页面空白
- 如果
document.querySelector()返回null,说明data-tab值和面板id不一致,脚本会卡在.hidden = false报错,建议加空值判断
用 hidden 属性切换比 display: none 更语义化且轻量
hidden 是 HTML5 原生布尔属性,浏览器原生支持,设置 el.hidden = true 等价于 display: none,但更语义、更安全。它不会触发重排(re-flow),也不会让 iframe/video 继续缓冲(这点 visibility: hidden 做不到)。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 所有面板初始加
hidden,如<div id="panel-about" hidden> <li>JS 切换只操作 <code>.hidden布尔值,不用碰 CSS 类或 style - 若需过渡动画(如淡入),配合 CSS 的
:not([hidden])选择器 +opacity和transition即可 - 注意:
hidden不影响tabindex,隐藏后仍可被键盘聚焦,需同步设tabindex="-1"或移除 - 每次点击都先执行
document.querySelectorAll('[role="tabpanel"]').forEach(p => p.hidden = true) - 再查出目标面板并设
targetEl.hidden = false - 如果内容区有
<input>,切走后再切回时焦点会丢失——这不是 bug,是hidden的设计行为;如需保留焦点,得手动保存并恢复focus() - 避免用
querySelectorAll('.tab-pane')这类 class 匹配,容易误伤其他无关区块;优先用[role="tabpanel"]这种语义化定位 - 外层容器加
role="tablist" - 每个按钮加
role="tab"、aria-controls="panel-id",当前激活项额外加aria-selected="true"并设tabindex="0",其余为tabindex="-1" - 对应面板加
role="tabpanel"、aria-labelledby="tab-id",确保与按钮 ID 对应 - 监听
keydown,对ArrowLeft/ArrowRight实现键盘导航,别指望浏览器自动处理
切换前先统一隐藏所有面板,再单独显示目标项
很多人漏掉“统一隐藏”这步,只写 targetEl.hidden = false,结果多个面板同时可见。核心逻辑不是“显示这个”,而是“只显示这个”。
实操建议:
立即学习“前端免费学习笔记(深入)”;
无障碍必须补全 aria 属性,否则键盘用户根本用不了
纯鼠标点选的标签页对屏幕阅读器和键盘用户基本不可用。Tab 键进不去按钮、方向键切不了页签、读屏软件不知道当前在哪——这些不是锦上添花,是基础可用性底线。
实操建议:
立即学习“前端免费学习笔记(深入)”;
最易被忽略的是首次加载状态同步和无障碍属性的完整性。很多效果看着动了,但一按 Tab 键就断连,或者服务端渲染后 JS 没补全 aria-selected,导致读屏器读错当前页签。这些不是“高级技巧”,是切换功能能被真实用户使用的前提。



















