纯css标签页核心是radio的:checked伪类驱动,需同名name、精确id/for匹配,面板须为radio同级后续兄弟元素,用~或+选择器控制显隐,隐藏radio时须兼顾可访问性。

用 :checked + 隐藏单选按钮控制标签页显隐
纯 CSS 标签页的核心不是靠 hover,而是靠表单控件状态驱动——input[type="radio"] 的 :checked 伪类。它能稳定触发、支持键盘导航(Tab 切换)、天然互斥,比 :hover 更可靠,也比 :target 更可控。
关键前提是:所有 radio 必须同名(name="tab"),且每个对应一个 id 相匹配的面板。常见错误是漏写 name 或拼错 id/for,导致点击无响应。
- radio 必须放在 label 之前或之后,但
for属性必须精确指向其id - 面板(
.panel)默认用display: none,仅当对应 radio 为:checked时才设为display: block - 不能用
visibility: hidden替代display: none,否则屏幕阅读器仍会读出隐藏内容
~ 和 + 兄弟选择器决定面板能否被定位
CSS 无法“向上”或“跨父级”选择,所以面板元素必须和 radio 在同一父容器内,且位于 radio 之后。若结构是 <label><input></label><div class="panel">,那 <code>input:checked ~ .panel 就失效——因为 .panel 不是 input 的兄弟,而是 label 的兄弟。
正确结构应为:
<input type="radio" id="tab1" name="tab" checked><label for="tab1">首页</label> <div class="panel">首页内容</div> <input type="radio" id="tab2" name="tab"><label for="tab2">产品</label> <div class="panel">产品内容</div>
此时 #tab1:checked ~ .panel 才能命中第一个面板;而 #tab2:checked ~ .panel 会命中**所有后续**的 .panel(包括第一个),所以需配合 :nth-of-type 或更精确的 class 控制。
用 :has() 实现反向联动(现代浏览器可用)
如果想让某个面板显示时,其上方的 label 自动高亮,传统 CSS 做不到——因为无法“选中父元素的兄弟”。但 Chrome 105+、Firefox 121+、Safari 15.4+ 已支持 :has(),可写成:
label:has(+ .panel:is(:target, :first-child)) {
background-color: #007bff;
}
不过生产环境慎用::has() 性能开销略高,且旧版浏览器完全不支持。更稳妥的做法仍是把状态逻辑交给 radio,并用相邻选择器统一控制 label 样式:
#tab1:checked + label { background: #007bff; }#tab1:checked + label + .panel { display: block; }
这样 label 和 panel 的样式更新完全同步,无竞态,也不依赖 JS。
移动端焦点与可访问性容易被忽略
纯 CSS 标签页在手机上常出问题:点击 tab 后面板没显示,或键盘 Tab 无法聚焦到 radio。根本原因往往是 input 被设了 position: absolute; left: -9999px 却没加 clip-path: inset(50%) 或 clip: rect(1px, 1px, 1px, 1px),导致部分 iOS Safari 屏幕阅读器跳过该控件。
推荐隐藏方式(兼顾语义与兼容):
input[type="radio"] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
另外,别忘了给 label 加 cursor: pointer 和足够点击热区(至少 44×44px),否则小屏点不准。











