配了三年交互状态,今天 CSS 终于把「打开还是关闭」这件事原生化了——:open 伪类把打开状态彻底从 JS 里拆出来了

你每次写展开面板、下拉菜单、对话框的时候,第一步是不是先在 JS 里写 isOpen = !isOpen,然后再给元素挂一个 .is-open 或者 [data-state="open"] 的 class?

这个流程,今天被 CSS 彻底接管了。

:open 是什么

CSS Selectors Level 4 定义了一个 :open 伪类,它匹配所有「当前处于打开状态」的元素,覆盖这些 HTML 场景:

  • <details open> — 折叠面板
  • <dialog> — 对话框(无论通过 .showModal() 还是 .show() 打开)
  • <select> — 下拉选择框(打开时匹配)
  • <input type="date"><input type="time"> 等日期/时间选择器
  • 所有带 open 属性的可切换元素

一个属性,把语义状态直接暴露给 CSS,不需要任何 JS 介入。

:popover-open 不是一回事

2026 年 8 月 Baseline 的 :open 容易和 :popover-open 混淆,后者是专门给 <popover> 元素准备的。区别在于:

  • :open 覆盖 <details><dialog><select>、日期选择器等原生可折叠元素
  • :popover-open 只匹配 <popover> 属性元素的「显示状态」

两者并列存在,对应不同的语义树。

5 行代码把展开面板的样式写完

<details> + :open 做一个带动画的折叠面板:

/* 默认收起状态:箭头向右 */
summary::after {
  content: "→";
  transition: transform 0.2s;
}

/* 展开状态:箭头向下,背景变浅蓝 */
details[open] {
  background: #f0f7ff;
  border-color: #3b82f6;
}

details[open] summary::after {
  transform: rotate(90deg);
}

/* 父容器在展开时加高亮 */
details:has([open]) {
  box-shadow: 0 2px 12px rgba(59, 130, 246, 0.2);
}

HTML:

<details>
  <summary>点击展开更多设置</summary>
  <div class="settings-panel">
    <label><input type="checkbox"> 启用通知</label>
    <label><input type="checkbox"> 暗黑模式</label>
  </div>
</details>

不需要 is-open class,不需要 JS 监听 toggle 事件,CSS 自己读元素的打开状态。

<dialog> 也有了独立的状态选择器

对话框以前只能靠 JS 的 .showModal().close() 驱动样式,现在 CSS 可以直接追踪:

/* 对话框打开时,背景加模糊遮罩 */
dialog[open] {
  opacity: 1;
  transform: scale(1);
}

/* 或者用 :open 更通用(dialog 打开时隐式带 open 属性) */
dialog:open {
  background: white;
  border-radius: 12px;
}

注意:dialog 元素在 .show() 调用时会隐式添加 open 属性,.close() 时移除。所以 :open<dialog> 天然生效,不需要在 HTML 里写 open

为什么比 JS 方案更合理

传统 JS 方案的流程是:用户点击 → JS 修改变量 → JS 更新 DOM class → CSS 读取 class 改变样式。这条链路上,任何一步出错都会导致「点了没反应」或者「样式对不上」。

:open 的流程是:用户点击 → 浏览器更新元素状态 → CSS 直接读取浏览器管理的状态。没有中间变量,没有 class 同步,没有 race condition。

更重要的是,这是语义级别的状态:open 匹配的是「元素当前是否展开」这个语义事实,而不是「某个变量是不是 true」。屏幕阅读器、搜索引擎爬虫早就把这些元素的 open 属性当作语义信号来处理,现在 CSS 也在用同一套信号。

浏览器支持情况

截至 2026 年 8 月,:open 在以下版本稳定支持:

  • Chrome 122+(2024年3月)
  • Firefox 128+(2024年7月)
  • Safari 17.2+(2024年1月)
  • Edge 122+(2024年3月)

覆盖率达到 Baseline「广泛可用」标准,即所有主流浏览器在所有活跃版本均已支持。对于现代项目,可以直接使用,无需 @supports 降级。

下一步:把状态逻辑搬回 CSS

如果你的项目里还有「点击展开 → JS 切换 class → CSS 读 class 变样式」这套流程,今天就是迁移的时机:

  1. is-open class 换成原生 <details><dialog>
  2. [data-state="open"] 选择器换成 :open
  3. 删除 JS 里的 classList.toggle 和状态变量
  4. :has(:open) 处理父容器联动

结果:代码更短,逻辑更干净,语义更正确。


结语: 浏览器在 2026 年把「交互状态」这件事彻底还给了平台。:open 让 CSS 直接读语义状态,不需要 JS 做中间人。这不是 CSS 在抢 JS 的活——是 CSS 在做它本来就该做的事:给元素的语义状态找到对应的样式表达。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 13 阅读