Combobox 可搜索选择
用于可搜索选择场景的 MoriUI 组件。
资源
导入
import { Combobox, ComboboxAnchor, ComboboxCancel, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxSeparator, ComboboxTrigger, ComboboxViewport } from 'moriui'组合方式
MoriUI 使用 Reka 的 Anchor、Input、Content、Viewport 与 Item 组合;不提供 Base UI 的 List、Collection 或 Chips API。
自定义项目
ComboboxItem 的 value 使用真实 AcceptableValue;复杂视觉可放入 Item,但筛选文本仍应清晰。
多选
multiple 时 v-model 是值数组,选择后浮层保持打开;当前 MoriUI 不发布 Base UI Chips 子组件。
无效与禁用
在 ComboboxInput 设置 aria-invalid,在 Combobox 根设置 disabled;行为与状态来自 Reka UI。
RTL
dir 会同时影响输入、弹层定位与键盘逻辑方向。
示例
概览
可筛选的单选框架列表。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected">
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
基础
Anchor、Input、Viewport 与 Item 的最小组合。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected">
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
多选
multiple 与数组 v-model 管理多个选择。
当前选择:Vue
<script setup lang="ts">
import { ref } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = ref<string[]>(['Vue'])
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected" multiple>
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected.join('、') || '无' }}
</p>
</div>
</template>
清空
ComboboxCancel 清空筛选并按配置重置值。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxCancel,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected" reset-model-value-on-clear>
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxCancel aria-label="清空搜索" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
分组
Group、Label 与 Separator 组织选项。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxLabel,
ComboboxSeparator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const groups = [
{ label: '前端框架', items: ['Vue', 'React', 'Svelte'] },
{ label: '服务端框架', items: ['Nuxt', 'Next.js'] }
]
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected">
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<template v-for="(group, index) in groups" :key="group.label">
<ComboboxGroup>
<ComboboxLabel>{{ group.label }}</ComboboxLabel>
<ComboboxItem v-for="item in group.items" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxGroup>
<ComboboxSeparator v-if="index < groups.length - 1" />
</template>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
自定义项目
在 ComboboxItem 中组合丰富内容。
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxTrigger,
ComboboxViewport,
Item,
ItemContent,
ItemDescription,
ItemTitle
} from 'moriui'
const selected = shallowRef<string>()
const countries = [
{ value: 'cn', label: '中国', region: '亚洲' },
{ value: 'fr', label: '法国', region: '欧洲' },
{ value: 'ca', label: '加拿大', region: '北美洲' }
]
</script>
<template>
<Combobox v-model="selected">
<ComboboxAnchor><ComboboxInput aria-label="国家" placeholder="搜索国家…" /><ComboboxTrigger aria-label="打开国家列表" /></ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配国家。</ComboboxEmpty><ComboboxItem v-for="country in countries" :key="country.value" :value="country.value">
<Item size="xs" class="p-0">
<ItemContent><ItemTitle>{{ country.label }}</ItemTitle><ItemDescription>{{ country.region }} · {{ country.value }}</ItemDescription></ItemContent>
</Item>
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
</template>
无效
输入使用 aria-invalid。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected">
<ComboboxAnchor>
<ComboboxInput aria-invalid="true" aria-label="选择框架" placeholder="选择框架" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
禁用
根组件 disabled 阻止所有交互。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected" disabled>
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
高亮
Reka UI 管理高亮项目与键盘导航。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div class="w-full max-w-xs">
<Combobox v-model="selected" :highlight-on-hover="true">
<ComboboxAnchor>
<ComboboxInput aria-label="选择框架" placeholder="搜索框架…" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
独立触发器
Trigger 打开包含搜索输入的弹层。
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const countries = ['中国', '日本', '新加坡']
</script>
<template>
<Combobox v-model="selected">
<ComboboxTrigger class="w-64" aria-label="选择国家">
{{ selected ?? '选择国家' }}
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput aria-label="搜索国家" placeholder="搜索…" />
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty><ComboboxItem v-for="country in countries" :key="country" :value="country">
{{ country }}
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
</template>
带图标输入
Anchor 内组合图标、输入和触发器。
<script setup lang="ts">
import { shallowRef } from 'vue'
import { Globe } from '@lucide/vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const zones = ['亚洲/上海', '欧洲/巴黎', '美洲/纽约']
</script>
<template>
<Combobox v-model="selected" class="w-full max-w-xs">
<ComboboxAnchor><Globe aria-hidden="true" /><ComboboxInput aria-label="时区" placeholder="选择时区" /><ComboboxTrigger aria-label="打开时区列表" /></ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>没有匹配时区。</ComboboxEmpty><ComboboxItem v-for="zone in zones" :key="zone" :value="zone">
{{ zone }}
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
</template>
RTL
搜索与弹层在 RTL 上下文中工作。
当前选择:无
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxTrigger,
ComboboxViewport
} from 'moriui'
const selected = shallowRef<string>()
const frameworks = ['Vue', 'Nuxt', 'SvelteKit', 'Astro']
</script>
<template>
<div dir="rtl" class="w-full max-w-xs">
<Combobox v-model="selected">
<ComboboxAnchor>
<ComboboxInput dir="rtl" aria-label="الفئات" placeholder="أضف فئات" />
<ComboboxTrigger aria-label="打开选项" />
</ComboboxAnchor>
<ComboboxContent dir="rtl">
<ComboboxViewport>
<ComboboxEmpty>没有匹配项。</ComboboxEmpty>
<ComboboxItem v-for="item in frameworks" :key="item" :value="item">
{{ item }}<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
<p class="mt-2 text-xs text-muted-foreground">
当前选择:{{ selected ?? '无' }}
</p>
</div>
</template>
API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| Combobox v-model | AcceptableValue | AcceptableValue[] | undefined / [] | 单选或 multiple 多选值。 |
| Combobox v-model:open | boolean | undefined | 受控浮层开关状态。 |
| multiple / disabled | boolean | false | 多选模式与整体禁用状态。 |
| openOnClick / openOnFocus | boolean | true / false | 输入交互是否打开浮层。 |
| ignoreFilter | boolean | false | 关闭 Reka 内置筛选,交给调用方过滤。 |
| resetModelValueOnClear | boolean | false | 清空筛选时是否同步清空选择值。 |
| ComboboxItem value | AcceptableValue | — | 必填项目值。 |
| ComboboxContent side / align | PopperContentProps | 'bottom' / 'start' | Reka Popper 的定位属性。 |
无障碍说明
Combobox 复用 Reka UI 的 combobox/listbox ARIA、活动后代、方向键、Enter 选择、Escape 关闭与 Teleport。输入必须有名称;无结果文案、项目文本和禁用/无效状态都应可被辅助技术理解。