响应式 Tabs 应用同一套 DOM 与 @media 控制显隐:大屏显示横向 Tab 栏(display: flex),小屏切换为下拉菜单(display: none/block),需严格互斥;结构上 input[type="checkbox"] 必须紧邻 label 和 dropdown-menu,配合 :checked + ~ 实现无 JS 下拉;面板需同步管理 display、aria-hidden 和 tabindex 以保障可访问性;SPA 中 :target 不可靠,应改用 location.hash 或 location.replace。

用同一套 DOM + @media 控制显隐,别写两套结构
响应式 Tabs 的核心不是“小屏换组件”,而是让同一组 HTML 在不同断点下呈现不同形态:大屏显示横向 Tab 栏,小屏切换为下拉菜单。关键在显隐逻辑必须互斥且纯 CSS 驱动——@media 规则里,.tab-list 和 .dropdown-menu 的 display 值要严格反向设置。
常见错误是只隐藏下拉菜单,却忘了给 Tab 栏加 display: none(小屏下),或反之。结果小屏出现两个控件,大屏残留下拉浮层。
- 小屏(如
max-width: 768px):.tab-list { display: none; },.dropdown-menu { display: block; } - 大屏:
.tab-list { display: flex; },.dropdown-menu { display: none; } - 确保
.dropdown-menu紧跟<input type="checkbox">后面,才能用input:checked ~ .dropdown-menu选中 - 父容器避免设
overflow: hidden,否则下拉内容会被裁剪
:checked + ~ 实现无 JS 下拉展开,结构必须紧邻
不用 JS 就能控制下拉显隐,靠的是 <input type="checkbox"> 和相邻兄弟选择器。这个方案兼容 IE9+,比 :focus-within 更稳,但对 HTML 结构敏感。
容易踩的坑是 <label for="xxx"> 没正确绑定 <input id="xxx">,或中间插入了其他元素打断了“紧邻”关系,导致点击无响应。
立即学习“前端免费学习笔记(深入)”;
-
<input id="tab-toggle" type="checkbox">必须放在最前 -
<label for="tab-toggle">必须紧跟其后,且不能有其他标签隔开 -
<div class="dropdown-menu">必须直接跟在<input>后面,中间不能有文本、注释或其他元素 - 下拉菜单收起时,
input应为未勾选态,否则键盘用户按空格会意外触发
Tab 面板隐藏不能只靠 display: none
单纯用 display: none 隐藏非激活面板,会让屏幕阅读器跳过内容、搜索引擎无法索引、JS 获取不到 offsetHeight。可访问性要求必须同步管理 aria-hidden 和 tabindex。
- 非激活面板:同时加
style="display: none"、aria-hidden="true"、tabindex="-1" - 激活面板:移除
display: none,设aria-hidden="false",去掉tabindex="-1" - 下拉菜单里的选项要用
role="menu"+role="menuitem",↑/↓ 键导航需极简 JS 补齐——纯 CSS 做不到这一步 - 移动端需加
touch-action: manipulation防延迟,overscroll-behavior: contain防穿透
:target 方案在 SPA 中大概率失效,别盲目套用
:target 看似简单,但只在静态页面、原生哈希跳转时可靠。React/Vue 项目里,router.push("/page#tab2") 或 history.pushState() 不会触发 :target 匹配,因为 fragment 变更没真正写入地址栏。
真正生效的前提是浏览器地址栏真实变更且 DOM 中已存在对应 id 元素——异步挂载的面板、大小写不一致的 id、用 class 替代 id,都会让匹配失败。
- SPA 中必须用
window.location.hash = "#tab2"才能触发,这是最轻量且兼容的写法 - 若用
pushState,得补一句location.replace("#" + id)做兜底 -
scroll-margin-top只对被滚动到的那个具体id元素生效,写在父容器上无效 - 想加淡入效果,用
opacity+transition,别依赖display切换——它不触发过渡
aria- 属性和紧邻关系里。


















