刚接触HTML的折叠面板时,谁没被这两个标签整懵过?明明想用做个手风琴菜单,结果里面的Flexbox和Grid布局死活不按套路出牌。更别提那个,想在里面加个链接或者按钮,屏幕读屏软件直接罢工。这俩货到底啥来头?就是个能展开收起的容器,它必须搭配作为标题区。不写?浏览器会自己造一个,但HTML校验器会报红。而默认长得很像按钮,点一下就能切换的展开收起状态。可问题就出在这里:当变成网格或弹性盒子容器时,里面的内容块就像中了邪。
布局翻车现场
打开浏览器开发工具瞅瞅,默认是个块级元素。给套上display: grid,再分成三行,第三行用1fr想占满剩余空间。比如写这么一段:
<style>
.custom-details {
display: grid;
grid-template-rows: auto auto 1fr;
height: 40vh;
}
</style>
<details class="custom-details" open>
<summary>标题区</summary>
<div>第一块内容</div>
<div>第二块内容,应该把剩余高度撑满</div>
</details>结果第三行并没有像预期那样顶上去。为啥?扒一下HTML规范:内部有一个影子树(shadow tree),里面藏了两个插槽(slots)。第二个插槽专门放之后的所有内容。当没有open属性时,第二个插槽被强制加上display: block; content-visibility: hidden;。就算加了open属性让样式移除,可这些插槽里的元素仍然被浏览器当作普通块盒处理,Grid容器没法把栅格行为传递给它们。这就像给集装箱画好了格子,但里面的货物箱子死活不肯按格子摆放。
解决办法:别直接在上设置Grid或Flexbox。改用一个包裹层。在外面套个<div>,把布局样式加给这个外层。或者把的display改成contents,让它自身不产生盒子,然后让内部的直接子元素(也就是影子树暴露出来的插槽内容)去响应外层布局。更稳妥的土办法:把需要布局的内容全部塞进后面的唯一一个块级容器里,给这个容器设置Grid或Flex。示例:
<style>
.fix-details {
display: block; /* 不改details本体 */
}
.fix-details .inner-grid {
display: grid;
grid-template-rows: auto auto 1fr;
height: 40vh;
}
</style>
<details class="fix-details" open>
<summary>点击展开</summary>
<div class="inner-grid">
<div>第一行</div>
<div>第二行</div>
<div>第三行会撑开</div>
</div>
</details>这样第三行就能老老实实占满剩余高度了。记住一个铁律:只负责展开收起的交互逻辑,布局的脏活累活全交给它里面的子容器。
菜单翻车现场
GitHub带起了一股风气——用做下拉菜单。代码大概长这样:
<details>
<summary>菜单</summary>
<ul>
<li><a href="#">选项一</a></li>
<li><a href="#">选项二</a></li>
</ul>
</details>看着挺合理,但拿屏幕读屏软件(比如JAWS)一测就露馅。当处于收起状态时,里面的链接完全不在可访问性树里。按Tab键聚焦到,读屏只念“菜单 按钮”。再按Tab?焦点居然还在上,里面的链接就像不存在。只有把展开后,那些链接才会被读出来。更离谱的是,页面内搜索(Ctrl+F)在Chromium以外的浏览器里根本搜不到折叠区里的文字。
原因:被规范归类为交互元素,但它内部折叠的内容在DOM里其实一直都在,只是被浏览器用content-visibility: hidden藏起来了。辅助技术对待这种隐藏内容的方式不一致。有些直接忽略,有些勉强能读但状态混乱。
解决方案:别拿当菜单容器。改用真正的菜单组件,比如用<button>加JavaScript控制下拉面板。如果非要硬用,至少得加上ARIA属性来补救:
<details>
<summary aria-haspopup="true" aria-expanded="false">菜单</summary>
<ul role="menu">
<li role="menuitem"><a href="#">选项一</a></li>
<li role="menuitem"><a href="#">选项二</a></li>
</ul>
</details>但这也是个补丁方案,治标不治本。更推荐的做法:写一个自定义的折叠组件,用<div>和<button>控制展开状态,配合aria-controls和aria-expanded,再加点transition动画,体验比顺滑多了。
给summary塞链接会怎样
有人想偷懒,直接在里包个<a>链接:
<details>
<summary><a href="/page">点我跳转</a> 还有箭头图标</summary>
内容在这里
</details>HTML校验器不会报错,因为规范允许的内容模型是“短语内容”或“标题内容”,链接属于短语内容,所以语法上合法。但实际用键盘操作:按Tab键,焦点先到链接上,读屏念“点我跳转 链接”。再按Tab,焦点跑到自带的按钮角色上,读屏又念“点我跳转 按钮”。同一个文本被念两遍,而且链接的跳转功能和按钮的展开功能打架。点一下链接,页面跳转了,但还没来得及展开。这体验简直裂开。
如果往里塞<input>或者<button>,情况更糟。多个可聚焦元素挤在里,键盘焦点会乱窜,读屏软件直接逻辑死机。
唯一靠谱的做法:里只放纯文本和不可交互的图标(比如用<span>模拟的图标)。所有可交互的东西(链接、按钮、输入框)必须放在之后的折叠内容区里。这是血泪教训换来的铁规矩。
拿个表格对比一下不同写法的翻车程度:
| 写法 | 键盘焦点 | 读屏体验 | 是否推荐 |
|---|---|---|---|
| summary纯文本 | 正常 | 清晰 | 推荐 |
| summary包链接 | 双重焦点 | 重复朗读 | 别用 |
| summary包按钮 | 混乱 | 状态丢失 | 别用 |
| summary包输入框 | 无法预测 | 基本废了 | 千万别 |
最后说句大实话:和这俩标签最适合的场景就是“附加说明”、“折叠评论区”、“隐藏长引用”这类简单玩意儿。非要用它们做复杂菜单或者嵌套交互组件,那就是在雷区蹦迪。等浏览器厂商把那些影子树的坑填平了,再考虑高级玩法也不迟。
