details[open] summary是唯一可靠触发旋转的选择器,因它精准匹配DOM真实状态;需配transition和display:inline,隐藏原生三角须三管齐下,::before插入图标更可控且兼容性好。

details[open] summary 是唯一能可靠触发旋转的选择器
直接写 summary:hover 或 summary:active 都不行——用户可能用 JS 设置 open = true,此时没鼠标动作,伪类根本不会匹配;浏览器原生 summary 也不响应 :active。只有 details[open] summary 精准对应 DOM 的真实状态,不管展开是点的、JS 设的,还是服务端渲染带 open 属性的,它都生效。
别用 summary::after 或 summary::marker 自己读 open 状态,它们没法感知父级属性变化。必须靠父选择器联动。
transform: rotate() 必须配 transition 和 display: inline
transform: rotate(90deg) 单独写只是静态摆位,不会动。得提前在 summary 上声明过渡:
summary {
display: inline;
transition: transform 0.2s ease;
}
details[open] summary {
transform: rotate(90deg);
}
漏掉 display: inline 很容易翻车:有些 CSS 框架(比如重置了 all: unset)会让 summary 失去默认行内行为,transform 就可能错位或失效。
立即学习“前端免费学习笔记(深入)”;
- 别写
transition: all——会把padding、margin也加动画,内容区域“抽搐” - 统一用
rotate(0deg)/rotate(90deg)切换,避免浮点精度导致跳帧 - 绕中心转是默认行为,不用额外设
transform-origin
隐藏原生小三角要分浏览器双保险
Chrome/Firefox 支持 summary::marker { display: none },但 Safari 15.4 之前完全不认这个伪元素。稳妥做法是三管齐下:
summary {
list-style: none;
padding-left: 0;
}
summary::marker {
content: "";
}
summary::-webkit-details-marker {
all: unset;
}
其中 all: unset 是关键,否则老版 Chrome/Safari 里 ::-webkit-details-marker 的默认内边距和对齐会干扰图标位置。
如果用了自定义图标(比如 summary::before),记得在 details[open] summary::before 里同时改 content 和 transform,否则字符切换和旋转会打架。
用 ::before 插入图标比依赖 ::marker 更可控
当项目要兼容 iOS 16(Safari 15.4 以下)或需要精确调色、定位、动画时,summary::before 是目前最稳的 fallback 方案:
summary {
list-style: none;
position: relative;
padding-left: 24px;
}
summary::before {
content: "▶";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
transition: transform 0.2s;
}
details[open] summary::before {
content: "▼";
transform: translateY(-50%) rotate(90deg);
}
这里 top: 50%; transform: translateY(-50%) 比 line-height 垂直居中更可靠,尤其文字行高动态变化时。千万别用 margin-left 推图标——动画会强制重排,卡顿明显。
真正容易被忽略的是:旋转动画是否生效,不只看 CSS 写没写对,更取决于 summary 是否保持语义化交互能力。一旦你给它加 display: block 或 float: left,键盘用户按空格/回车就失灵,屏幕阅读器也读不出“click to toggle”。



















