---
description: "表单里的选项一多，普通下拉就没法扫了。combobox 把选择拆进一个弹出层：顶部一个过滤输入框，下面一列选项，键入收窄、键盘挑选、点中即写值收弹层。国家、负责人、关联记录这类字段，就是它"
---

# 搜索下拉（可搜索单选 combobox）

表单里的选项一多，普通下拉就没法扫了。combobox 把选择拆进一个弹出层：顶部一个过滤输入框，下面一列选项，键入收窄、键盘挑选、点中即写值收弹层。国家、负责人、关联记录这类字段，就是它。

<iframe src="/skills/dashboard/assets/demo-combobox.html"
        style="width:100%;height:560px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：搜索下拉"></iframe>

试这几下：

- 点开**国家 / 地区**，键入过滤 —— ↑↓ 移动高亮、Enter 选中、Esc 关闭并归还焦点；点弹层外任意处也收起
- 选中后再点开：高亮自动停在**已选项**上（带 ✓）；触发器右侧的 × 一键清除
- **负责人（异步）**每次键入都模拟 300ms 请求：先 loading，快速连打只落最后一次结果
- 键入一串乱码看空态；两个字段都留空直接保存，看错误位

## 规矩

- **字段契约与 TextField/SelectField 一致**：label / 错误位 / 值一个形状，选中即写值并关闭弹层。demo 用受控 props + 局部 state 管值，真实版把它包进 `FormField`，值和校验交给表单系统（TanStack Form + zod）
- **高亮和选中是两回事**：高亮（`aria-activedescendant`）只是键盘光标 —— 随过滤收窄自动回位、打开时停在当前选中项；选中才是写进表单的值
- **键盘可达**：弹层打开后焦点落在过滤输入框上，↑↓ / Enter / Esc 全程不碰鼠标；列表用 `role="listbox"` / `role="option"` 播报。demo 手写这套按键，真实版由 Popover + 列表托管焦点
- **异步选项由过滤词驱动**：demo 用一个 300ms 定时器兼作防抖与模拟延迟 —— 新键入清掉上一次，天然丢弃过期响应；真实版换成 `useDebouncedSearch` + 数据请求，把结果喂给 `options`

> 蓝本：`add-field-combobox.md`[^add-field-combobox]（open-dashboard @ `aa9815f`，MIT，Invariants 已消化进上面「规矩」）

??? abstract "demo 源码：`assets/demo-combobox.html`（自包含、未压缩）"

    ```html
    --8<-- "skills/dashboard/assets/demo-combobox.html"
    ```

[^add-field-combobox]: [`add-field-combobox.md`](https://github.com/ahpxex/open-dashboard/blob/aa9815f426e9520841c4aa15a341bfd9f866091f/.claude/skills/add-component/references/add-field-combobox.md)，2026-07-28 读。
