表单

Select 下拉选择

用于下拉选择场景的 MoriUI 组件。

资源

导入

import { Select, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SelectViewport } from 'moriui'

组合方式

Select 组合 Trigger/Value 与 Teleport 的 Content/Viewport;项目由 Item、ItemText 和 Indicator 组成。

定位方式

SelectContent 使用 position="item-aligned" 对齐已选项,或使用默认 popper 相对触发器定位。

分组与滚动

Group、Label 与 Separator 组织长列表;Viewport 和上下滚动按钮处理超出可用高度的选项。

禁用与无效

根组件 disabled 阻止全部交互,Item 也可单独禁用;Trigger 使用 aria-invalid 表达无效状态。

RTL

dir 同时影响触发器、弹层定位、选项阅读和方向键行为。

示例

概览

组合水果选项与受控 v-model。

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import {
    Select,
    SelectContent,
    SelectGroup,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectLabel,
    SelectTrigger,
    SelectValue,
    SelectViewport
  } from 'moriui'

  const fruit = shallowRef<string>()
  const fruits = [
    { label: '苹果', value: 'apple' },
    { label: '香蕉', value: 'banana' },
    { label: '蓝莓', value: 'blueberry' },
    { label: '葡萄', value: 'grapes' },
    { label: '菠萝', value: 'pineapple' }
  ]
</script>

<template>
  <Select v-model="fruit">
    <SelectTrigger aria-label="水果" class="w-full max-w-48">
      <SelectValue placeholder="选择水果" />
    </SelectTrigger>
    <SelectContent>
      <SelectViewport>
        <SelectGroup>
          <SelectLabel>水果</SelectLabel>
          <SelectItem v-for="item in fruits" :key="item.value" :value="item.value">
            <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
          </SelectItem>
        </SelectGroup>
      </SelectViewport>
    </SelectContent>
  </Select>
</template>

对齐已选项

用真实 position API 切换 item-aligned 与 popper 定位。

关闭后改为相对触发器定位。

<script setup lang="ts">
  import { computed, shallowRef } from 'vue'
  import {
    Field,
    FieldContent,
    FieldDescription,
    FieldGroup,
    FieldLabel,
    Select,
    SelectContent,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectTrigger,
    SelectValue,
    SelectViewport,
    Switch
  } from 'moriui'

  const fruit = shallowRef('banana')
  const alignSelectedItem = shallowRef(true)
  const position = computed(() => alignSelectedItem.value ? 'item-aligned' as const : 'popper' as const)
  const fruits = [
    { value: 'apple', label: '苹果' },
    { value: 'banana', label: '香蕉' },
    { value: 'blueberry', label: '蓝莓' },
    { value: 'grapes', label: '葡萄' }
  ]
</script>

<template>
  <FieldGroup class="w-full max-w-xs">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldLabel for="align-selected-item">
          对齐已选项
        </FieldLabel>
        <FieldDescription>关闭后改为相对触发器定位。</FieldDescription>
      </FieldContent>
      <Switch id="align-selected-item" v-model="alignSelectedItem" />
    </Field>
    <Field>
      <Select v-model="fruit">
        <SelectTrigger aria-label="水果">
          <SelectValue />
        </SelectTrigger>
        <SelectContent :position="position">
          <SelectViewport>
            <SelectItem v-for="item in fruits" :key="item.value" :value="item.value">
              <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
            </SelectItem>
          </SelectViewport>
        </SelectContent>
      </Select>
    </Field>
  </FieldGroup>
</template>

分组

Group、Label 与 Separator 区分水果和蔬菜。

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import {
    Select,
    SelectContent,
    SelectGroup,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectLabel,
    SelectSeparator,
    SelectTrigger,
    SelectValue,
    SelectViewport
  } from 'moriui'

  const food = shallowRef<string>()
  const fruits = [
    { value: 'apple', label: '苹果' },
    { value: 'banana', label: '香蕉' },
    { value: 'blueberry', label: '蓝莓' }
  ]
  const vegetables = [
    { value: 'carrot', label: '胡萝卜' },
    { value: 'broccoli', label: '西兰花' },
    { value: 'spinach', label: '菠菜' }
  ]
</script>

<template>
  <Select v-model="food">
    <SelectTrigger aria-label="食材" class="w-full max-w-48">
      <SelectValue placeholder="选择食材" />
    </SelectTrigger>
    <SelectContent>
      <SelectViewport>
        <SelectGroup>
          <SelectLabel>水果</SelectLabel>
          <SelectItem v-for="item in fruits" :key="item.value" :value="item.value">
            <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
          </SelectItem>
        </SelectGroup>
        <SelectSeparator />
        <SelectGroup>
          <SelectLabel>蔬菜</SelectLabel>
          <SelectItem v-for="item in vegetables" :key="item.value" :value="item.value">
            <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
          </SelectItem>
        </SelectGroup>
      </SelectViewport>
    </SelectContent>
  </Select>
</template>

可滚动

Viewport 与滚动按钮承载跨地区时区列表。

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import {
    Select,
    SelectContent,
    SelectGroup,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectLabel,
    SelectScrollDownButton,
    SelectScrollUpButton,
    SelectTrigger,
    SelectValue,
    SelectViewport
  } from 'moriui'

  const timezone = shallowRef<string>()
  const regions = [
    { label: '亚洲', items: [{ value: 'shanghai', label: '上海' }, { value: 'tokyo', label: '东京' }, { value: 'seoul', label: '首尔' }, { value: 'singapore', label: '新加坡' }, { value: 'kolkata', label: '加尔各答' }] },
    { label: '欧洲', items: [{ value: 'london', label: '伦敦' }, { value: 'paris', label: '巴黎' }, { value: 'berlin', label: '柏林' }, { value: 'madrid', label: '马德里' }, { value: 'rome', label: '罗马' }] },
    { label: '美洲', items: [{ value: 'new-york', label: '纽约' }, { value: 'chicago', label: '芝加哥' }, { value: 'denver', label: '丹佛' }, { value: 'los-angeles', label: '洛杉矶' }, { value: 'sao-paulo', label: '圣保罗' }] }
  ]
</script>

<template>
  <Select v-model="timezone">
    <SelectTrigger aria-label="时区" class="w-full max-w-64">
      <SelectValue placeholder="选择时区" />
    </SelectTrigger>
    <SelectContent>
      <SelectScrollUpButton />
      <SelectViewport>
        <SelectGroup v-for="region in regions" :key="region.label">
          <SelectLabel>{{ region.label }}</SelectLabel>
          <SelectItem v-for="item in region.items" :key="item.value" :value="item.value">
            <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
          </SelectItem>
        </SelectGroup>
      </SelectViewport>
      <SelectScrollDownButton />
    </SelectContent>
  </Select>
</template>

禁用

根组件 disabled 阻止打开与选择。

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import {
    Select,
    SelectContent,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectTrigger,
    SelectValue,
    SelectViewport
  } from 'moriui'

  const fruit = shallowRef('banana')
</script>

<template>
  <Select v-model="fruit" disabled>
    <SelectTrigger aria-label="水果" class="w-full max-w-48">
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectViewport>
        <SelectItem v-for="item in [['apple', '苹果'], ['banana', '香蕉'], ['grapes', '葡萄']]" :key="item[0]" :value="item[0]">
          <SelectItemIndicator /><SelectItemText>{{ item[1] }}</SelectItemText>
        </SelectItem>
      </SelectViewport>
    </SelectContent>
  </Select>
</template>

无效

Trigger 的 aria-invalid 与 FieldError 提供完整错误反馈。

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import {
    Field,
    FieldError,
    FieldLabel,
    Select,
    SelectContent,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectTrigger,
    SelectValue,
    SelectViewport
  } from 'moriui'

  const fruit = shallowRef<string>()
</script>

<template>
  <Field class="w-full max-w-48" data-invalid>
    <FieldLabel for="invalid-fruit">
      水果
    </FieldLabel>
    <Select v-model="fruit">
      <SelectTrigger id="invalid-fruit" aria-invalid="true">
        <SelectValue placeholder="选择水果" />
      </SelectTrigger>
      <SelectContent>
        <SelectViewport>
          <SelectItem v-for="item in [['apple', '苹果'], ['banana', '香蕉'], ['blueberry', '蓝莓']]" :key="item[0]" :value="item[0]">
            <SelectItemIndicator /><SelectItemText>{{ item[1] }}</SelectItemText>
          </SelectItem>
        </SelectViewport>
      </SelectContent>
    </Select>
    <FieldError>请选择一种水果。</FieldError>
  </Field>
</template>

RTL

阿拉伯语分组选项在 RTL 弹层中工作。

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import {
    Select,
    SelectContent,
    SelectGroup,
    SelectItem,
    SelectItemIndicator,
    SelectItemText,
    SelectLabel,
    SelectSeparator,
    SelectTrigger,
    SelectValue,
    SelectViewport
  } from 'moriui'

  const food = shallowRef<string>()
  const fruits = [
    { value: 'apple', label: 'تفاح' },
    { value: 'banana', label: 'موز' },
    { value: 'blueberry', label: 'توت أزرق' }
  ]
  const vegetables = [
    { value: 'carrot', label: 'جزر' },
    { value: 'broccoli', label: 'بروكلي' },
    { value: 'spinach', label: 'سبانخ' }
  ]
</script>

<template>
  <Select v-model="food" dir="rtl">
    <SelectTrigger aria-label="الطعام" class="w-40">
      <SelectValue placeholder="اختر الطعام" />
    </SelectTrigger>
    <SelectContent dir="rtl">
      <SelectViewport>
        <SelectGroup>
          <SelectLabel>الفواكه</SelectLabel>
          <SelectItem v-for="item in fruits" :key="item.value" :value="item.value">
            <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
          </SelectItem>
        </SelectGroup>
        <SelectSeparator />
        <SelectGroup>
          <SelectLabel>الخضروات</SelectLabel>
          <SelectItem v-for="item in vegetables" :key="item.value" :value="item.value">
            <SelectItemIndicator /><SelectItemText>{{ item.label }}</SelectItemText>
          </SelectItem>
        </SelectGroup>
      </SelectViewport>
    </SelectContent>
  </Select>
</template>

API

属性 类型 默认值 说明
Select v-modelAcceptableValue | AcceptableValue[]undefined / []单选或 multiple 多选值。
Select v-model:openbooleanundefined受控弹层开关状态。
Select defaultValue / defaultOpenAcceptableValue | AcceptableValue[] / booleanundefined / false非受控初始值与初始打开状态。
Select multiple / disabledbooleanfalse多选模式与整体禁用状态。
Select bystring | ((a, b) => boolean)undefined对象值的比较字段或比较函数。
SelectTrigger size'sm' | 'default''default'触发器尺寸。
SelectContent position'item-aligned' | 'popper''popper'弹层相对已选项或触发器的定位方式。
SelectContent side / alignPopperContentProps'bottom' / 'center'popper 模式的方向与对齐。
SelectItem value / disabledAcceptableValue / boolean— / false必填项目值与单项禁用状态。

无障碍说明

Select 复用 Reka UI 的 listbox ARIA、焦点管理、类型搜索、方向键、Enter/Space 选择、Escape 关闭与触发器焦点归还。Trigger 必须有名称,ItemText 应提供清晰文本,错误需配合 aria-invalid 与可读说明。

关联组件