Select 选择器
Select 展示可折叠的选项列表,并允许用户从中选择一项。
用法
import { Select } from "@lenso/ui";此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { SelectExample, states } from "./select-example";export function Default() { return <SelectExample label="State" choices={states} />;}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import {Select, Label, Description, Header, ListBox, Separator} from "@lenso/ui";
export default () => ( <Select> <Label /> <Select.Trigger> <Select.Value /> <Select.ClearButton /> <Select.Indicator /> </Select.Trigger> <Description /> <Select.Popover> <ListBox> <ListBox.Item> <Label /> <Description /> <ListBox.ItemIndicator /> </ListBox.Item> <ListBox.Section> <Header /> <ListBox.Item> <Label /> </ListBox.Item> </ListBox.Section> </ListBox> </Select.Popover> </Select>);示例
变体
Select 组件支持两种视觉变体:
primary(默认)— 带阴影的标准样式,适用于大多数场景secondary— 低强调、无阴影,适合在 Surface 等表面背景上使用
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { exampleStyles, SelectExample } from "./select-example";const choices = [ { value: "option1", label: "Option 1" }, { value: "option2", label: "Option 2" },];export function Variants() { return ( <div {...stylex.props(exampleStyles.stack)}> <SelectExample label="Primary variant" choices={choices} variant="primary" /> <SelectExample label="Secondary variant" choices={choices} variant="secondary" /> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
宽度充满
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { exampleStyles, SelectExample } from "./select-example";export function FullWidth() { return ( <div {...stylex.props(exampleStyles.wide, exampleStyles.stack)}> <SelectExample fluid label="Favorite Animal" choices={["Cat", "Dog", "Bird"].map((label) => ({ label, value: label.toLowerCase() }))} /> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带描述
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { SelectExample, states } from "./select-example";export function WithDescription() { return ( <SelectExample label="State" choices={states} description="Select your state of residence" /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
必填
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { Form } from "@base-ui/react/form";import * as stylex from "@stylexjs/stylex";import { countrySections, exampleStyles, SelectExample, states } from "./select-example";export function Required() { return ( <Form {...stylex.props(exampleStyles.field, exampleStyles.stack)} onSubmit={(event) => { event.preventDefault(); alert("Form submitted successfully!"); }} > <SelectExample fluid required name="state" label="State" choices={states} /> <SelectExample fluid required name="country" label="Country" placeholder="Select a country" choices={countrySections .flatMap((section) => section.items) .filter((item) => ["usa", "canada", "mexico", "uk", "france", "germany"].includes(item.value), )} /> <button type="submit" {...stylex.props(exampleStyles.action)}> Submit </button> </Form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
禁用
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { countries, exampleStyles, SelectExample, states } from "./select-example";export function Disabled() { return ( <div {...stylex.props(exampleStyles.stack)}> <SelectExample disabled label="State" choices={states} defaultValue="california" /> <SelectExample disabled multiple label="Countries to Visit" choices={countries.slice(0, 6)} placeholder="Select countries" defaultValue={["argentina", "japan", "france"]} /> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
含禁用选项
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { SelectExample } from "./select-example";const animals = ["Dog", "Cat", "Bird", "Kangaroo", "Elephant", "Tiger"].map((label) => ({ label, value: label.toLowerCase(), disabled: label === "Cat" || label === "Kangaroo",}));export function WithDisabledOptions() { return <SelectExample label="Animal" placeholder="Select an animal" choices={animals} />;}Local adaptation source above. Derived from HeroUI v3.2.6 source.
多选
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { countries, SelectExample } from "./select-example";export function MultipleSelect() { return ( <SelectExample multiple label="Countries to Visit" placeholder="Select countries" choices={countries} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
分组选项
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { countrySections, SelectExample } from "./select-example";export function WithSections() { return ( <SelectExample label="Country" placeholder="Select a country" choices={countrySections.flatMap((section) => section.items)} sections={countrySections} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控组件
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { useState } from "react";import { controlledStates, exampleStyles, SelectExample } from "./select-example";export function Controlled() { const [state, setState] = useState<string | null>("california"); return ( <div {...stylex.props(exampleStyles.compactStack)}> <SelectExample label="State (controlled)" choices={controlledStates} placeholder="Select a state" value={state} onValueChange={setState} /> <p {...stylex.props(exampleStyles.note)}> Selected: {controlledStates.find((item) => item.value === state)?.label || "None"} </p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控多选
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { useState } from "react";import { controlledStates, exampleStyles, SelectExample } from "./select-example";export function ControlledMultiple() { const [selected, setSelected] = useState<string[]>(["california", "texas"]); return ( <div {...stylex.props(exampleStyles.stack)}> <SelectExample multiple label="States (controlled multiple)" choices={controlledStates} placeholder="Select states" value={selected} onValueChange={setSelected} /> <p {...stylex.props(exampleStyles.note)}> Selected: {selected.length > 0 ? selected.join(", ") : "None"} </p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控展开状态
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { useState } from "react";import { exampleStyles, SelectExample, states } from "./select-example";export function ControlledOpenState() { const [open, setOpen] = useState(false); return ( <div {...stylex.props(exampleStyles.stack)}> <SelectExample label="State" choices={states} open={open} onOpenChange={setOpen} /> <button type="button" {...stylex.props(exampleStyles.action)} onClick={() => setOpen(!open)}> {open ? "Close" : "Open"} Select </button> <p {...stylex.props(exampleStyles.note)}>Select is {open ? "open" : "closed"}</p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
异步加载
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** * HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 * Modified: abortable native fetch and scroll pagination replace RAC collection/load-more. */import { Spinner } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useCallback, useEffect, useRef, useState } from "react";import { exampleStyles, SelectExample } from "./select-example";interface PokemonPage { next: string | null; results: { name: string }[];}export function AsynchronousLoading() { const [pokemon, setPokemon] = useState<{ name: string }[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const cursor = useRef<string | null>("https://pokeapi.co/api/v2/pokemon"); const controller = useRef<AbortController | null>(null); const load = useCallback(async () => { if (!cursor.current || controller.current) return; const request = new AbortController(); controller.current = request; setLoading(true); setError(false); try { const response = await fetch(cursor.current, { signal: request.signal }); if (!response.ok) throw new Error(`Pokemon request failed (${response.status})`); const page: PokemonPage = await response.json(); cursor.current = page.next; setPokemon((previous) => [...previous, ...page.results]); } catch { if (!request.signal.aborted) setError(true); } finally { if (controller.current === request) { controller.current = null; setLoading(false); } } }, []); useEffect(() => { void load(); return () => { controller.current?.abort(); controller.current = null; }; }, [load]); return ( <SelectExample label="Pick a Pokemon" placeholder="Select a Pokemon" choices={pokemon.map(({ name }) => ({ value: name, label: name }))} onPopoverScroll={(event) => { const element = event.currentTarget; if (element.scrollHeight - element.scrollTop - element.clientHeight < 48) void load(); }} footer={ <div {...stylex.props(exampleStyles.loading)}> {loading && ( <> <Spinner size="sm" /> <span {...stylex.props(exampleStyles.note)}>Loading more...</span> </> )} {!loading && cursor.current && ( <button type="button" {...stylex.props(exampleStyles.action)} onClick={() => void load()} > {error ? "Retry loading" : "Load more"} </button> )} </div> } /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义指示器
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { ChevronsExpandVertical } from "@gravity-ui/icons";import { SelectExample, states } from "./select-example";export function CustomIndicator() { return ( <SelectExample label="State" choices={states} indicator={<ChevronsExpandVertical width={12} height={12} aria-hidden="true" />} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带清除按钮
Select.Trigger 本身是 button,因此不能再嵌套 <button> 来实现清除。请在触发器内组合 Select.ClearButton — 它渲染为非 button 控件,且不会打开菜单。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { useState } from "react";import { SelectExample, states } from "./select-example";export function WithClearButton() { const [value, setValue] = useState<string | null>("california"); return ( <SelectExample label="State" choices={states} value={value} onValueChange={setValue} clear={value === null ? undefined : () => setValue(null)} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
由于 ARIA 将 button 的子节点视为纯展示内容,清除控件无法获得焦点,因此它带有 aria-hidden,仅作为指针操作的可视入口。当组合了 Select.ClearButton 时,触发器同时支持按 Backspace 或 Delete 清除,这是键盘与屏幕阅读器用户的清除方式。两种方式都会调用 onClear。
自定义展示值
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { Avatar, AvatarImage, AvatarFallback } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { exampleStyles, SelectExample } from "./select-example";const users = [ { id: "1", name: "Bob", email: "[email protected]", color: "blue" }, { id: "2", name: "Fred", email: "[email protected]", color: "green" }, { id: "3", name: "Martha", email: "[email protected]", color: "purple" }, { id: "4", name: "John", email: "[email protected]", color: "red" }, { id: "5", name: "Jane", email: "[email protected]", color: "orange" },];function UserAvatar({ user, small = false }: { user: (typeof users)[number]; small?: boolean }) { return ( <Avatar size="sm" xstyle={small ? exampleStyles.smallAvatar : undefined}> <AvatarImage src={`https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/${user.color}.jpg`} alt="" /> <AvatarFallback>{user.name.charAt(0)}</AvatarFallback> </Avatar> );}export function CustomValue() { return ( <SelectExample label="User" placeholder="Select a user" choices={users.map((user) => ({ value: user.id, label: user.name, content: ( <div {...stylex.props(exampleStyles.row)}> <UserAvatar user={user} /> <div {...stylex.props(exampleStyles.details)}> <span>{user.name}</span> <span {...stylex.props(exampleStyles.note)}>{user.email}</span> </div> </div> ), }))} valueContent={(value) => { const user = users.find((item) => item.id === value); return user ? ( <span {...stylex.props(exampleStyles.row)}> <UserAvatar user={user} small /> <span>{user.name}</span> </span> ) : ( "Select a user" ); }} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
渲染函数
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { SelectExample, states } from "./select-example";export function RenderFunction() { return <SelectExample customRender label="State" choices={states} />;}Local adaptation source above. Derived from HeroUI v3.2.6 source.
表面样式
在 Surface 内使用时,请使用 variant="secondary",以应用适合表面背景的低强调变体。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { Form } from "@base-ui/react/form";import * as stylex from "@stylexjs/stylex";import { countrySections, exampleStyles, SelectExample, states } from "./select-example";export function OnSurface() { return ( <div {...stylex.props(exampleStyles.surface)}> <Form {...stylex.props(exampleStyles.stack)} onSubmit={(event) => { event.preventDefault(); alert("Form submitted successfully!"); }} > <SelectExample fluid required name="state" label="State" choices={states} variant="secondary" /> <SelectExample fluid required name="country" label="Country" placeholder="Select a country" choices={countrySections .flatMap((section) => section.items) .filter((item) => ["usa", "canada", "mexico", "uk", "france", "germany"].includes(item.value), )} variant="secondary" /> <button type="submit" {...stylex.props(exampleStyles.action)}> Submit </button> </Form> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. Copyright 2026 HeroUI. SPDX-License-Identifier: Apache-2.0 */import { SelectExample } from "./select-example";export function CustomStyles() { return ( <SelectExample custom label="Plan" placeholder="Pick a plan" variant="secondary" choices={[ { value: "free", label: "Free" }, { value: "pro", label: "Pro" }, ]} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
若要自定义组件类,可使用 @layer components 指令。了解更多。
@layer components { .select { @apply flex flex-col gap-1; }
.select__trigger { @apply rounded-lg border border-border bg-surface p-2; }
.select__value { @apply text-current; }
.select__indicator { @apply text-muted; }
.select__popover { @apply rounded-lg border border-border bg-surface p-2; }}样式参考
HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。
CSS 类
Select 组件使用以下 CSS 类(查看源码样式):
基础类 [!toc]
.select- Select 根容器.select__trigger- 打开下拉的触发按钮.select__value- 当前显示的值或占位符.select__clear-button- 触发器内的可选清除控件.select__indicator- 下拉指示图标.select__popover- 弹出层容器
变体类 [!toc]
.select--primary- Primary 变体,带阴影(默认).select--secondary- Secondary 变体,无阴影,适合在 Surface 上使用
状态类 [!toc]
.select[data-invalid="true"]- 无效状态.select__trigger[data-focus-visible="true"]- 触发器聚焦状态.select__trigger[data-disabled="true"]- 触发器禁用状态.select__value[data-placeholder="true"]- 占位符状态.select__clear-button[data-empty="true"]- 无选中项时隐藏清除控件.select__indicator[data-open="true"]- 展开时的指示器状态
交互状态
该组件同时支持 CSS 伪类与 data 属性,以提供更灵活的状态控制:
- 悬停:触发器上的
:hover或[data-hovered="true"] - 聚焦:触发器上的
:focus-visible或[data-focus-visible="true"] - 禁用:Select 上的
:disabled或[data-disabled="true"] - 展开:指示器上的
[data-open="true"]
API 参考
Select
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
placeholder | string | 'Select an item' | Select 为空时显示的占位符文本。 |
selectionMode | "single" | "multiple" | "single" | 启用单选或多选。 |
isOpen | boolean | - | 设置菜单是否打开(受控)。 |
defaultOpen | boolean | - | 设置菜单默认是否打开(非受控)。 |
onOpenChange | (isOpen: boolean) => void | - | 展开状态变化时的事件处理函数。 |
disabledKeys | Iterable<Key> | - | 禁用条目的 key。 |
isDisabled | boolean | - | Select 是否禁用。 |
value | Key | Key[] | null | - | 当前值(受控)。 |
defaultValue | Key | Key[] | null | - | 默认值(非受控)。 |
onChange | (value: Key | Key[] | null) => void | - | 值变化时的事件处理函数。 |
onClear | () => void | - | 清除选择时的事件处理函数。 |
isRequired | boolean | - | 用户输入是否必填。 |
isInvalid | boolean | - | Select 的值是否无效。 |
name | string | - | 输入框名称,用于提交 HTML 表单。 |
autoComplete | string | - | 描述自动完成行为类型。 |
fullWidth | boolean | false | Select 是否占满容器宽度。 |
variant | "primary" | "secondary" | "primary" | 视觉变体。primary 为默认带阴影样式。secondary 为低强调、无阴影变体,适合在 Surface 上使用。 |
className | string | - | 额外的 CSS 类。 |
children | ReactNode | RenderFunction | - | Select 内容或渲染函数。 |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, SelectRenderProps> | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
Select.Trigger
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 额外的 CSS 类。 |
children | ReactNode | RenderFunction | - | 触发器内容或渲染函数。 |
Select.Value
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 额外的 CSS 类。 |
children | ReactNode | RenderFunction | - | 值区域内容或渲染函数。 |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, SelectValueRenderProps> | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
Select.Indicator
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 额外的 CSS 类。 |
children | ReactNode | - | 自定义指示器内容。 |
Select.ClearButton
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 额外的 CSS 类。 |
children | ReactNode | - | 自定义内容,替代默认图标。 |
onClick | (e: MouseEvent) => void | - | 点击清除控件时的事件处理函数。 |
Select.ClearButton 渲染为 span,因此可以放在 Select.Trigger(button)内部而不会形成嵌套 button。选择为空时会自动隐藏。
Select.Popover
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
placement | "bottom" | "bottom left" | "bottom right" | "bottom start" | "bottom end" | "top" | "top left" | "top right" | "top start" | "top end" | "left" | "left top" | "left bottom" | "start" | "start top" | "start bottom" | "right" | "right top" | "right bottom" | "end" | "end top" | "end bottom" | "bottom" | 弹出层相对触发器的位置。 |
className | string | - | 额外的 CSS 类。 |
children | ReactNode | - | 子内容。 |
RenderProps
对 Select.Value 使用渲染函数时,会提供以下值:
| Prop | 类型 | 描述 |
|---|---|---|
defaultChildren | ReactNode | 默认渲染的值。 |
isPlaceholder | boolean | 是否为占位符状态。 |
state | SelectState | Select 的状态。 |
selectedItems | Node[] | 当前已选中的条目。 |
无障碍
Select 组件实现 ARIA 列表框模式,并提供:
- 完整的键盘导航支持
- 选择变化时的屏幕阅读器播报
- 合理的焦点管理
- 禁用状态支持
- 输入首字母快速定位(typeahead)
- 与 HTML 表单的集成
更多信息见 React Aria Select 文档。