命令面板是“高级感”功能里最容易做出形、丢出神的:视觉上一眼像模像样,键盘用户却在三米外迷路。本文把验收一个命令面板需要过的问题列成清单,每一条都对应真实实现过的代码。
1. 唤起与关闭
- 快捷键:
Ctrl/Cmd + K全局监听;在input/textarea/[contenteditable]聚焦时不劫持其它单键快捷键。 - 关闭途径必须至少三条:
Esc、遮罩点击、显式关闭按钮。面板内的按钮不要用tabindex="-1"把自己藏起来。 - 关闭时清理:移除焦点圈监听、恢复 body 滚动锁、移除临时 toast。
2. 语义:dialog + combobox 两层结构
外层面板是模态:
<div
role="dialog"
aria-modal="true"
aria-labelledby="palette-title"
hidden
></div>
内层输入框是组合框(combobox),结果列表是 listbox,两者用 aria-controls / aria-activedescendant 关联:
<input
role="combobox"
aria-expanded="true"
aria-controls="palette-results"
aria-activedescendant="option-3"
/>
<div id="palette-results" role="listbox">
<a id="option-3" role="option" aria-selected="true">…</a>
</div>
关键决策:选项用 aria-activedescendant 而不是让焦点真的落进列表。焦点始终留在输入框,上下键只移动“激活”状态。这样输入法组合、退格编辑都不会丢,也是 ARIA 作者工具箱里 listbox 模式的正统做法。
3. 焦点:去哪了,怎么回来
打开面板:
- 记录
document.activeElement作为恢复锚点; - 焦点进入输入框(
focus({ preventScroll: true }),避免弹层刚出现就滚动页面); - 焦点陷阱:监听
Tab,到达最后一个可聚焦元素后回卷到第一个(Shift+Tab反向)。aria-modal="true"只是给读屏器的声明,不能阻止 Tab 走出弹层,陷阱必须自己写。
关闭面板:焦点还给锚点。跳过这一步,键盘用户会被扔回页面顶部。
4. 键盘协议全表
| 键 | 行为 |
|---|---|
| ↑ / ↓ | 移动激活项,scrollIntoView({ block: 'nearest' }) 保证可见 |
| Enter | 激活当前项(链接跳转或执行动作命令) |
| Esc | 关闭并恢复焦点 |
| Tab | 在面板内圈闭移动(访问关闭按钮等) |
列表为空时上下键应当无操作而不是报错;只有一个结果时 Enter 直接命中,省一次按键。
5. 加载态与空状态是设计的一部分
索引异步加载时,面板不能装作“无结果”。三态文案:
- 索引未到:
正在加载站内索引…(可先渲染无需索引的导航推荐项,索引到达后增量合并); - 有输入无匹配:
没有找到匹配内容。试试更短的关键词,或浏览推荐入口。; - 无输入:展示推荐入口 + 最近公开内容。
面板打开的视觉就绪不应等待任何 fetch——先聚焦、先渲染骨架,网络只是后续的数据补全。
6. 跳转不要整页刷新
面板内导航若用 window.location.href =,会把站点的 View Transitions 客户端路由整个旁路掉:整页重载、过渡动画丢失、SPA 状态清零。同域跳转改为程序化锚点点击,让路由拦截器接管:
const link = document.createElement('a');
link.href = target;
document.body.append(link);
link.click();
link.remove();
路由不可用时浏览器自动回退为普通导航,零额外成本。
7. 移动端与 reduced-motion
- 宽度断点:≤640px 时面板改为全宽贴顶,内边距收紧,选项网格从三列降为一列;
- 触屏用户没有 Esc:遮罩点击与关闭按钮权重上升,关闭区触控高度不低于 44px;
prefers-reduced-motion: reduce:关闭选项 hover 位移与面板缩放入场,保留边框高亮等静态可辨状态。
8. 验收脚本思路
把上表变成可回归的测试:Playwright 下 Control+k → 断言 document.activeElement 为输入框 → 连按 12 次 Tab → 断言焦点仍在 #command-palette 内 → 输入关键词 Enter → 断言 URL 变化且 window 上的标记变量存活(证明 SPA 导航未重载)。比人工点击可靠,也比快照测试更能抓住“焦点逃逸”这类逻辑缺陷。
小结
命令面板的“高级感”来自毫秒级的响应与丝滑动画,而它的专业底线是那几条看不见的规矩:焦点进得来、出不去、还得回来;空与加载各有各的话;跳转不破坏路由。先把规矩跑通,再谈惊艳。