HTML 又变酷了:认识 Invoker Commands API
原文:HTML is getting cool again: Meet the Invoker Commands API | 作者:ale3oula
📝 摘要
文章揭示了一个反差:整个行业都在追逐 AI,而浏览器却不断推出让平台更强大的原生能力,让开发者可以删掉 JavaScript、减少状态、直接在 HTML 里表达 UI 行为。作者以「打开对话框需要一堆样板代码」为切入点,介绍新的 Invoker Commands API——通过 commandFor 和 command 两个属性,声明式地把行为赋给按钮,从而无需事件监听器就能控制 dialog、popover 等交互元素。核心观点是:这不是「浏览器变弱」,而是我们终于可以少写 JS,把职责交还给浏览器。
📋 术语表
| 英文 | 中文 | 说明 |
|---|---|---|
| Invoker Commands API | 调用器命令 API | 声明式地把行为赋给按钮、进而控制交互元素的新 Web API |
| commandFor / commandfor | commandFor 属性 | 将按钮变为「命令调用器」,值为被控制元素的 ID |
| popover | 弹出层 | 浏览器原生支持的浮层元素,通过 popover 属性声明 |
| dialog | 对话框 | 浏览器原生模态对话框元素 |
| semantic HTML | 语义化 HTML | 使用有意义的标签和属性来表达文档结构 |
| landmark | 地标 | 用于标识页面主要区域的语义角色(如 nav、main) |
| declarative | 声明式 | 直接描述「是什么/要什么」,而非编写实现步骤 |
| Baseline 2025 | 基线 2025 | MDN 的浏览器兼容性分级,表示跨主流浏览器已广泛可用 |
正文(双语对照)
Exposes how LLMs miss modern browser capabilities
揭示了 LLM 是如何错过现代浏览器能力的。
For years, frontend development has had a slightly embarrassing relationship with HTML. We all read about semantic HTML, we talk about using the right landmarks, the right attributes, accessible forms, and meaningful elements. And then we go back to writing React.
多年来,前端开发与 HTML 的关系一直有点尴尬。我们都读过语义化 HTML,都在谈论要用对地标、用对属性、可访问的表单和有意义元素。然后,我们又回去写 React 了。
While the whole industry is obsessed with AI, browsers are shipping features that make the platform even more capable. Features that let us remove JS, reduce state, and express UI behaviour in HTML. But since AI is trained in the past, these are ignored or not recommended enough through old patterns.
当整个行业都在痴迷 AI 时,浏览器却在持续交付让平台更强大的功能。这些功能让我们能删掉 JS、减少状态、在 HTML 里直接表达 UI 行为。但由于 AI 训练的是过去的数据,这些功能被忽略了,或者没有被旧模式足够地推荐出来。
State boilerplate to open a dialog
打开一个对话框所需的状态样板代码
Historically, in order to open a dialog or a popover, we need a chunk of custom boilerplate code.
传统上,要打开一个对话框或弹出层,我们需要一堆自定义的样板代码。
<button id="open-dialog">Open dialog</button>
<dialog id="my-dialog">
<p>Dialog content</p>
<button id="close-dialog">Close</button>
</dialog>
<script>
const dialog = document.getElementById("my-dialog")
document.getElementById("open-dialog").addEventListener("click", () => {
dialog.showModal()
})
document.getElementById("close-dialog").addEventListener("click", () => {
dialog.close()
})
</script>We need a button to open our dialog, the dialog itself, and then we write JS to explain the interaction to the browser: “When the user clicks the button, open the dialog”.
我们需要一个按钮来打开对话框,对话框本身,然后还要写 JS 来向浏览器解释这个交互:「当用户点击按钮时,打开对话框」。
If you’re using React or Vue, this can get even more elaborate: You need some custom state, potentially pass it to your component, wire up an event handler, and then make sure everything stays in sync.
如果你用的是 React 或 Vue,这会变得更复杂:你需要一些自定义状态,可能要把它传给组件,接上事件处理器,然后确保一切都保持同步。
Whether vanilla or framework, the code above is completely reasonable on its own, every frontend developer has written something similar a hundred times. But after writing it for the thousandth time, it made me wonder: do we really need application state to represent that a dialog is open? The answer is: it depends. Sometimes state is needed. But sometimes we’re just rebuilding behaviour that HTML and the browser can already provide for us out of the box in 2026.
无论是原生还是框架,上面的代码本身完全合理,每个前端开发者都写过上百次类似的东西。但写到第一千次时,我不禁想:我们真的需要应用状态来表示「对话框是打开的」吗?答案是:看情况。有时确实需要状态。但有时,我们只是在重造 HTML 和浏览器在 2026 年早已开箱即用地为我们提供的行为。
Enters the chat: the Invoker Commands API
主角登场:Invoker Commands API
The Invoker Commands API provides us a way to declaratively assign behaviours to buttons, which then allows us to control these interactive elements. Instead of adding an event listener we can describe the relationship directly in HTML.
Invoker Commands API 为我们提供了一种声明式地把行为赋给按钮的方式,进而让我们能够控制这些交互元素。我们不必再添加事件监听器,而是直接在 HTML 里描述这种关系。
The attributes that help us are commandFor and command:
帮我们做到这一点的属性是 commandFor 和 command:
-
commandFor: turns our button into a “command invoker”. It takes the ID of the element to control as its value.
-
command: Specifies the action to be performed on that element.
-
commandFor:把我们的按钮变成一个「命令调用器」。它以被控制元素的 ID 作为值。 -
command:指定要在那个元素上执行的动作。
<button commandfor="mycoolpopover" command="toggle-popover">Toggle the popover</button>
<section id="mycoolpopover" popover>
<button commandfor="mycoolpopover" command="hide-popover">Close</button>
Awesome Popover content
</section>How to do it in React?
在 React 里怎么做?
All of these are transferable to your framework of choice:
所有这些都可以迁移到你选择的框架里:
export function DeleteButton() {
return (
<>
<button command="show-modal" commandFor="delete-dialog">
Delete account
</button>
<dialog id="delete-dialog">
<h2>Delete account?</h2>
<p>This cannot be undone.</p>
<button command="close" commandFor="delete-dialog">
Cancel
</button>
</dialog>
</>
)
}The important detail here is that React’s JSX property is commandFor, while the resulting HTML attribute is commandfor
这里有个重要细节:React 的 JSX 属性是 commandFor,而最终渲染出来的 HTML 属性是 commandfor。
Is that a really big deal?
这真的很重要吗?
At a first glance this looks like saving, maybe 10 lines of code. That’s nice. But the most interesting is the shift of the responsibilities from us, back to the browsers.
乍一看,这似乎只是省了大概 10 行代码。这固然不错。但最有趣的是职责的转移:从我们身上,交还给了浏览器。
The browser isn’t becoming less capable because we’re writing less JavaScript, but rather the opposite. We finally need less JS.
浏览器并没有因为我们少写 JavaScript 而变得更弱,恰恰相反。我们终于需要更少的 JS 了。
Browser support
浏览器支持
Now, before everyone starts deleting their dialogs, there is a small catch: The Invoker Commands API is new. MDN currently lists it as Baseline 2025, with cross-browser availability in the latest browser versions since December 2025. Older browsers may not support it.
现在,在大家开始删掉自己的对话框之前,有个小前提:Invoker Commands API 还很新。MDN 目前把它列为 Baseline 2025,自 2025 年 12 月起在最新浏览器版本中跨浏览器可用。较旧的浏览器可能不支持它。
References
参考
-
MDN — Invoker Commands API
-
MDN —
<dialog>element -
MDN —
<section>element -
MDN — Using the Popover API
-
MDN — Invoker Commands API
-
MDN —
<dialog>元素 -
MDN —
<section>元素 -
MDN — 使用 Popover API
译者注:本文作者 ale3oula 的核心视角很有启发——当整个行业忙着追 AI 时,浏览器平台本身一直在「变得更强」,让开发者能少写 JS。对正用 React/Tailwind 做前端的你来说,
commandFor/command这套声明式写法值得关注,尤其注意 JSX 里commandFor与 HTML 属性commandfor的大小写差异。目前兼容性是 Baseline 2025(2025 年 12 月起跨浏览器可用),老旧浏览器需降级处理。