原生 HTML 交互方案更简洁高效:details 元素自带展开/收起状态,无需 JS 管理;:target 伪类自动高亮锚点目标,省去 hashchange 监听;radio + :checked 可纯 CSS 实现单选导航面板。

用 details + summary 做折叠导航,真不用写 JS 状态逻辑
浏览器原生的 details 元素自带展开/收起状态,它不触发重绘、不依赖 useState 或 data-active,点击即切换,DOM 自己记着状态。适合侧边栏菜单、文档大纲、移动端抽屉式导航这类“开合即目的”的场景。
常见错误是强行给 summary 加 onclick="toggle()",或者用 div 模拟再手管 aria-expanded——其实完全没必要。
-
details默认关闭,加open属性可默认展开 -
summary必须是details的第一个子元素,否则语义失效,屏幕阅读器可能跳过 - 不要在
summary里嵌套button,会触发两次事件(click冒泡 +details自身 toggle) - 样式上,
summary::marker可自定义小箭头,但 Safari 对它的支持略保守,建议用background-image做降级
用 :target 实现锚点导航,连 class 切换都省了
当导航目标是页面内区块(比如 FAQ 页面点“价格”跳到 #pricing),:target 伪类会自动匹配当前 URL hash 对应的元素。你不需要监听 hashchange,也不用手动 add/remove active 类。
典型误用是给每个导航项写独立的 onclick 去 scroll 并高亮——其实只要保证 a[href="#section-id"] 和 section[id="section-id"] 严格对应,CSS 就能接管高亮:
立即学习“前端免费学习笔记(深入)”;
section:target {
animation: highlight 0.3s ease;
}
section:target h2 {
background-color: #f0f9ff;
}
-
:target只匹配完整 ID,#faq-1不会匹配id="faq" - 如果页面有多个同名 ID(虽然不该有),
:target只作用于第一个 - 配合
scroll-behavior: smooth在html上,滚动就带缓动,无需 JS
用 radio 组 + :checked 控制单选式导航面板
像产品功能对比页、多步骤表单的顶部标签栏,需要“一次只显示一个内容区”,用一组 input[type="radio"] 配合 :checked ~ .panel 就能纯 CSS 切换,状态由浏览器单选组天然保证。
别再用 data-tab="active" 配 JS 监听了——radio 的 name 属性就是状态键,value 就是状态值,干净又可靠。
- 所有
radio必须同名(name="nav-group"),否则无法互斥 - 用
for关联label和input,比直接点击input更易操作 -
.panel必须是radio同级或后续兄弟元素,才能用~选择器命中 - 初始状态靠给某个
radio加checked属性,而不是靠 JS 设置
为什么这些方案容易被忽略?因为开发者总在找“更强大”的解法
多数人一想交互就条件反射写 JS:监听、setState、classList.toggle、ref.current……但 HTML 原生交互属性(open、:target、:checked)的边界非常清晰——它们只管“状态映射”,不管“业务逻辑”。一旦你开始往里面塞异步加载、权限校验、埋点上报,就该让 JS 接手;但如果只是“点一下展开”“点链接跳到区块”“点标签切内容”,硬加 JS 反而引入冗余状态同步和兼容性补丁。
真正难的是判断分界点:这个交互,浏览器能不能自己记住?如果能,就让它记;如果不能,再写 JS。


















