表单

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-modelAcceptableValue | AcceptableValue[]undefined / []单选或 multiple 多选值。
Combobox v-model:openbooleanundefined受控浮层开关状态。
multiple / disabledbooleanfalse多选模式与整体禁用状态。
openOnClick / openOnFocusbooleantrue / false输入交互是否打开浮层。
ignoreFilterbooleanfalse关闭 Reka 内置筛选,交给调用方过滤。
resetModelValueOnClearbooleanfalse清空筛选时是否同步清空选择值。
ComboboxItem valueAcceptableValue必填项目值。
ComboboxContent side / alignPopperContentProps'bottom' / 'start'Reka Popper 的定位属性。

无障碍说明

Combobox 复用 Reka UI 的 combobox/listbox ARIA、活动后代、方向键、Enter 选择、Escape 关闭与 Teleport。输入必须有名称;无结果文案、项目文本和禁用/无效状态都应可被辅助技术理解。

关联组件