表单
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-model | AcceptableValue | AcceptableValue[] | undefined / [] | 单选或 multiple 多选值。 |
| Select v-model:open | boolean | undefined | 受控弹层开关状态。 |
| Select defaultValue / defaultOpen | AcceptableValue | AcceptableValue[] / boolean | undefined / false | 非受控初始值与初始打开状态。 |
| Select multiple / disabled | boolean | false | 多选模式与整体禁用状态。 |
| Select by | string | ((a, b) => boolean) | undefined | 对象值的比较字段或比较函数。 |
| SelectTrigger size | 'sm' | 'default' | 'default' | 触发器尺寸。 |
| SelectContent position | 'item-aligned' | 'popper' | 'popper' | 弹层相对已选项或触发器的定位方式。 |
| SelectContent side / align | PopperContentProps | 'bottom' / 'center' | popper 模式的方向与对齐。 |
| SelectItem value / disabled | AcceptableValue / boolean | — / false | 必填项目值与单项禁用状态。 |
无障碍说明
Select 复用 Reka UI 的 listbox ARIA、焦点管理、类型搜索、方向键、Enter/Space 选择、Escape 关闭与触发器焦点归还。Trigger 必须有名称,ItemText 应提供清晰文本,错误需配合 aria-invalid 与可读说明。