Select 选择器
@lark-ui/core
最喜爱的水果
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 带可见标签的基础选择器(Select)——推荐用法。 */
export function SelectBasicDemo() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      label="最喜爱的水果"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "苹果", banana: "香蕉", cherry: "樱桃" }}
    />
  );
}

安装

统一导出

import { Select } from "@lark-ui/core";

按需导出

import { Select } from "@lark-ui/core/components/select";

用法

import { Select } from "@lark-ui/core";

export default function Example() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      label="Favorite Fruit"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
    />
  );
}

示例

基础用法

带可见标签的选择器(Select)。提供 label 属性后,选择器会自动包裹在 Field 包装器中, 标签显示在其上方。

最喜爱的水果
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 带可见标签的基础选择器(Select)——推荐用法。 */
export function SelectBasicDemo() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      label="最喜爱的水果"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "苹果", banana: "香蕉", cherry: "樱桃" }}
    />
  );
}

尺寸

使用 size 属性可以与输入框(Input)的尺寸保持一致(xs、sm、base、lg)。

xs
sm
base
lg
import { Select } from "@lark-ui/core";

/** 选择器触发器的尺寸(xs/sm/base/lg),与输入框(Input)和组合框(Combobox)保持一致。 */
export function SelectSizesDemo() {
  return (
    <div className="grid gap-4">
      <div className="flex items-center gap-3">
        <span className="w-10 text-sm text-lark-subtle">xs</span>
        <Select
          aria-label="选择尺寸 xs"
          size="xs"
          className="w-[200px]"
          placeholder="请选择……"
          items={{ a: "选项 A", b: "选项 B" }}
        />
      </div>
      <div className="flex items-center gap-3">
        <span className="w-10 text-sm text-lark-subtle">sm</span>
        <Select
          aria-label="选择尺寸 sm"
          size="sm"
          className="w-[200px]"
          placeholder="请选择……"
          items={{ a: "选项 A", b: "选项 B" }}
        />
      </div>
      <div className="flex items-center gap-3">
        <span className="w-10 text-sm text-lark-subtle">base</span>
        <Select
          aria-label="选择尺寸 base"
          size="base"
          className="w-[200px]"
          placeholder="请选择……"
          items={{ a: "选项 A", b: "选项 B" }}
        />
      </div>
      <div className="flex items-center gap-3">
        <span className="w-10 text-sm text-lark-subtle">lg</span>
        <Select
          aria-label="选择尺寸 lg"
          size="lg"
          className="w-[200px]"
          placeholder="请选择……"
          items={{ a: "选项 A", b: "选项 B" }}
        />
      </div>
    </div>
  );
}

定位

弹层优先选择 side="bottom",当该方向空间不足时会自动翻转。使用 side、align、 sideOffset 和 alignOffset 可以明确固定其位置——碰撞处理仍然生效。

side=bottom(默认)
side=top
align=end
sideOffset=12
import { Select } from "@lark-ui/core";

/**
 * 使用 `side` 和 `align` 固定弹层位置。当所选方向空间不足时,位置仍会自动翻转。
 */
export function SelectPlacementDemo() {
  return (
    <div className="grid gap-4 sm:grid-cols-2">
      <Select
        label="side=bottom(默认)"
        className="w-[200px]"
        defaultValue="earth"
        items={planets}
      />
      <Select
        label="side=top"
        side="top"
        className="w-[200px]"
        defaultValue="earth"
        items={planets}
      />
      <Select
        label="align=end"
        align="end"
        className="w-[200px]"
        defaultValue="earth"
        items={planets}
      />
      <Select
        label="sideOffset=12"
        sideOffset={12}
        className="w-[200px]"
        defaultValue="earth"
        items={planets}
      />
    </div>
  );
}

与选中项对齐

设置 alignItemWithTrigger 后,弹层会覆盖在触发元素上,使选中的选项正好位于其上方, 效果与原生 select 一致。选中项之前的选项渲染在触发元素上方,其余选项渲染在下方, 因此弹层可以向上下两个方向延伸。下面的两个选择器都在列表中间选中了 Mars——打开它们 对比一下。当空间不足时,该模式会自动关闭并回退到常规的锚定定位。

锚定(默认)

在触发器下方打开

与选中项对齐

选中的选项与触发器对齐

import { Select } from "@lark-ui/core";

/**
 * `alignItemWithTrigger` 会让弹层覆盖在触发器上,使选中的选项正好位于触发器上方,
 * 与原生 `<select>` 一致。选中项之前的选项渲染在触发器上方,其余选项在下方。
 * 打开第二个选择器——「Mars」选在列表中部,因此弹层向上下两个方向展开。
 * 空间不足时会回退到常规的锚定定位方式。
 */
export function SelectDynamicPlacementDemo() {
  return (
    <div className="flex flex-wrap items-start gap-8">
      <Select
        label="锚定(默认)"
        description="在触发器下方打开"
        className="w-[200px]"
        defaultValue="mars"
        items={planets}
      />
      <Select
        label="与选中项对齐"
        description="选中的选项与触发器对齐"
        alignItemWithTrigger
        className="w-[200px]"
        defaultValue="mars"
        items={planets}
      />
    </div>
  );
}

无可见标签

当不需要可见标签时(例如在紧凑界面中,或上下文已经明确),请使用 aria-label 以保证无障碍。

import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 没有可见标签的选择器——请使用 aria-label 保证无障碍。 */
export function SelectWithoutLabelDemo() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      aria-label="选择一种水果"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "苹果", banana: "香蕉", cherry: "樱桃" }}
    />
  );
}

带描述

选择器与 Field 包装器集成,可在输入框下方显示描述文字。

问题类型

选择最能描述你问题的分类

import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 带标签和描述文字的选择器。 */
export function SelectWithDescriptionDemo() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Select
      label="问题类型"
      description="选择最能描述你问题的分类"
      className="w-[280px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        bug: "缺陷",
        documentation: "文档",
        feature: "功能",
      }}
    />
  );
}

带错误

传入 error 属性可显示校验错误。存在错误时,它会在界面上取代描述文字。

问题类型
请选择问题类型
import { Select } from "@lark-ui/core";

/** 带标签和校验错误的选择器。 */
export function SelectWithErrorDemo() {
  return (
    <Select
      label="问题类型"
      error="请选择问题类型"
      className="w-[280px]"
      value={null}
      items={{
        bug: "缺陷",
        documentation: "文档",
        feature: "功能",
      }}
    />
  );
}

占位符

使用 placeholder 属性可以在未选中任何值时显示提示文字。当使用 renderValue 自定义选中值的 显示方式时,值为 null 时会显示占位符,而不会调用 renderValue。

<Select
  placeholder="Select a user..."
  value={user}
  renderValue={(user) => user.name} // Only called when user is not null
/>
分类
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 未选中任何值时显示占位符的选择器。 */
export function SelectPlaceholderDemo() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Select
      label="分类"
      placeholder="请选择分类……"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        bug: "缺陷",
        documentation: "文档",
        feature: "功能",
      }}
    />
  );
}

带工具提示的标签

使用 labelTooltip 可以在标签旁添加工具提示图标,以补充说明上下文。

优先级
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 带标签工具提示的选择器,用于补充说明。 */
export function SelectWithTooltipDemo() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Select
      label="优先级"
      labelTooltip="优先级越高的问题越先得到处理"
      placeholder="选择优先级"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        low: "低",
        medium: "中",
        high: "高",
        critical: "紧急",
      }}
    />
  );
}

自定义渲染

使用 renderValue 可以自定义选中值在触发按钮中的显示方式。当处理的是复杂的对象数据结构 而非简单字符串值时,这会非常有用。

语言
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 使用自定义渲染展示复杂选项的选择器。 */
export function SelectCustomRenderingDemo() {
  const [value, setValue] = useState(languages[0]);

  return (
    <Select
      label="语言"
      className="w-[200px]"
      renderValue={(v) => (
        <span>
          {v.emoji} {v.label}
        </span>
      )}
      value={value}
      onValueChange={(v) => setValue(v as (typeof languages)[0])}
    >
      {languages.map((language) => (
        <Select.Option key={language.value} value={language}>
          {language.emoji} {language.label}
        </Select.Option>
      ))}
    </Select>
  );
}

renderValue 函数仅在选中有值时才会被调用。请使用 placeholder 定义未选中任何值时显示的内容。

选择器会将 value 与 items 逐一比较以找出当前选中项。对于对象类型的选项,默认比较的是是否为 同一引用,而非按值比较。如果希望按值比较对象选项,可以使用 isItemEqualToValue 属性。

<Select
  className="w-[200px]"
  placeholder="Select a language..."
  renderValue={(v) => (
    <span>
      {v.emoji} {v.label}
    </span>
  )}
  value={value}
  onValueChange={(v) => setValue(v)}
  // Provides custom comparison logic
  isItemEqualToValue={(item, value) => item.value === value.value}
>
  {languages.map((language) => (
    <Select.Option key={language.value} value={language}>
      {language.emoji} {language.label}
    </Select.Option>
  ))}
</Select>

加载状态

带加载状态的选择器组件。加载状态通过 loading 属性传给组件。

加载状态

从服务器加载(模拟 2 秒延迟)

负责人
import { Select } from "@lark-ui/core";

/** 处于加载状态的选择器。 */
export function SelectLoadingDemo() {
  return <Select aria-label="加载中的选择器" className="w-[200px]" loading />;
}

多选

使用 multiple 属性开启多选。此时 value 变为已选项组成的数组。请用 placeholder 定义空状态, 用 renderValue 自定义选中项的显示方式。

<Select
  multiple
  placeholder="Select columns..."
  value={selectedColumns}
  renderValue={(columns) => columns.join(", ")}
  onValueChange={setSelectedColumns}
>
  <Select.Option value="name">Name</Select.Option>
  <Select.Option value="email">Email</Select.Option>
</Select>
可见列
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 用于选择多个值的多选选择器。 */
export function SelectMultipleDemo() {
  const [value, setValue] = useState<string[]>(["Name", "Location", "Size"]);

  return (
    <Select
      label="可见列"
      className="w-[250px]"
      multiple
      renderValue={(value) => {
        if (value.length > 3) {
          return (
            <span className="line-clamp-1">
              {value.slice(0, 2).join("、") + ` 等 ${value.length - 2} 项`}
            </span>
          );
        }
        return <span>{value.join("、")}</span>;
      }}
      value={value}
      onValueChange={(v) => setValue(v as string[])}
    >
      <Select.Option value="Name">名称</Select.Option>
      <Select.Option value="Location">位置</Select.Option>
      <Select.Option value="Size">大小</Select.Option>
      <Select.Option value="Read">读取</Select.Option>
      <Select.Option value="Write">写入</Select.Option>
      <Select.Option value="CreatedAt">创建时间</Select.Option>
    </Select>
  );
}

更多示例

作者

选择本文档的主要作者

import { useState } from "react";
import { Select, Text } from "@lark-ui/core";

/** 使用复杂对象值和自定义选项渲染的选择器。 */
export function SelectComplexDemo() {
  const [value, setValue] = useState<(typeof authors)[0] | null>(null);

  return (
    <Select
      label="作者"
      description="选择本文档的主要作者"
      placeholder="选择作者"
      className="w-[200px]"
      onValueChange={(v) => setValue(v as (typeof authors)[0] | null)}
      value={value}
      isItemEqualToValue={(item, value) => item?.id === value?.id}
      renderValue={(author) => author.name}
    >
      {authors.map((author) => (
        <Select.Option key={author.id} value={author}>
          <div className="flex w-[300px] items-center justify-between gap-2">
            <Text>{author.name}</Text>
            <Text variant="secondary">{author.title}</Text>
          </div>
        </Select.Option>
      ))}
    </Select>
  );
}

禁用选项

可以使用 disabled 属性禁用选项。被禁用的选项会置灰,且无法被选中。

部署区域
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 带禁用选项(无法被选中)的选择器。 */
export function SelectDisabledOptionsDemo() {
  const [value, setValue] = useState<Region | null>(null);

  return (
    <Select
      label="部署区域"
      placeholder="请选择区域……"
      className="w-[250px]"
      value={value}
      onValueChange={(v) => setValue(v as Region | null)}
      isItemEqualToValue={(item, val) => item.value === val.value}
    >
      {regions.map((region) => (
        <Select.Option
          key={region.value}
          value={region}
          disabled={region.disabled}
        >
          {region.label}
        </Select.Option>
      ))}
    </Select>
  );
}

禁用选项(通过 items 属性)

items 对象映射属性既接受纯字符串值,也接受带有 disabled 的描述对象。

套餐
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 使用 items 属性并携带禁用描述符的选择器。 */
export function SelectDisabledItemsDemo() {
  const [value, setValue] = useState<string | null>("free");

  return (
    <Select
      label="套餐"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        free: "免费版",
        pro: "专业版",
        business: { label: "商业版", disabled: true },
        enterprise: { label: "企业版", disabled: true },
      }}
    />
  );
}

分组选项

使用 Select.Group、Select.GroupLabel 和 Select.Separator,可以用带标签的标题和视觉分隔线 来组织选项。

食物
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 按带标签的标题分组的选项组织方式的选择器。 */
export function SelectGroupedDemo() {
  const [value, setValue] = useState<Food | null>(null);

  return (
    <Select
      label="食物"
      placeholder="选择一种食物……"
      className="w-[220px]"
      value={value}
      onValueChange={(v) => setValue(v as Food | null)}
      isItemEqualToValue={(item, val) => item.value === val.value}
    >
      <Select.Group>
        <Select.GroupLabel>水果</Select.GroupLabel>
        {foods.fruits.map((food) => (
          <Select.Option key={food.value} value={food}>
            {food.label}
          </Select.Option>
        ))}
      </Select.Group>
      <Select.Separator />
      <Select.Group>
        <Select.GroupLabel>蔬菜</Select.GroupLabel>
        {foods.vegetables.map((food) => (
          <Select.Option key={food.value} value={food}>
            {food.label}
          </Select.Option>
        ))}
      </Select.Group>
    </Select>
  );
}

含禁用选项的分组

将分组、分隔线和禁用选项与信息工具提示结合使用,可以清晰区分可选与不可选的条目。

服务器区域
import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 分组选择器,含禁用选项和信息工具提示。 */
export function SelectGroupedWithDisabledDemo() {
  const [value, setValue] = useState<ServerRegion | null>(null);

  return (
    <Select
      label="服务器区域"
      placeholder="选择区域……"
      className="w-[260px]"
      value={value}
      onValueChange={(v) => setValue(v as ServerRegion | null)}
      isItemEqualToValue={(item, val) => item.value === val.value}
    >
      <Select.Group>
        <Select.GroupLabel>可用</Select.GroupLabel>
        {serverRegions.available.map((region) => (
          <Select.Option key={region.value} value={region}>
            {region.label}
          </Select.Option>
        ))}
      </Select.Group>
      <Select.Separator />
      <Select.Group>
        <Select.GroupLabel>不可用</Select.GroupLabel>
        {serverRegions.unavailable.map((region) => (
          <Select.Option key={region.value} value={region} disabled>
            {region.label}
          </Select.Option>
        ))}
      </Select.Group>
    </Select>
  );
}

长列表(滚动测试)

包含大量选项的选择器组件,用于测试弹层的滚动行为。弹层应平滑滚动,不出现回弹/过度滚动问题。

长列表选择器

测试大量选项时的滚动行为

import { useState } from "react";
import { Select } from "@lark-ui/core";

/** 使用长列表的选择器,用于测试弹层滚动行为。 */
export function SelectLongListDemo() {
  const [value, setValue] = useState<LongListItem | null>(null);

  return (
    <Select
      label="长列表选择器"
      description="测试大量选项时的滚动行为"
      placeholder="请选择一个选项……"
      className="w-[220px]"
      value={value}
      onValueChange={(v) => setValue(v as LongListItem | null)}
      isItemEqualToValue={(item, val) => item.value === val.value}
    >
      {longListItems.map((item) => (
        <Select.Option key={item.value} value={item}>
          {item.label}
        </Select.Option>
      ))}
    </Select>
  );
}

API 参考

Select

属性类型默认值说明
align"start" | "center" | "end"-弹出层相对指定边缘的对齐方式。
alignItemWithTriggerboolean-是否让定位层与触发元素重叠,使选中项文字与触发元素的取值文字对齐。仅对鼠标输入生效,空间不足时会自动禁用。
alignOffsetnumber | OffsetFunction-沿对齐轴的额外偏移量(像素)。也可以传入一个返回偏移量的函数,该函数可读取锚点元素与定位层元素的尺寸,以及它们的边缘和对齐方式。 该函数接收一个 `data` 对象参数,包含以下属性: - `data.anchor`:锚点元素的尺寸,包含 `width` 和 `height` 属性。 - `data.positioner`:定位层元素的尺寸,包含 `width` 和 `height` 属性。 - `data.side`:定位层对齐在锚点元素的哪一侧。 - `data.align`:定位层相对指定边缘的对齐方式。
anchorReactNode-弹出层定位所依据的元素。默认情况下,弹出层会相对于触发元素定位。
arrowPaddingnumber-箭头与弹出层边缘之间保持的最小距离。 用于防止箭头元素超出弹出层的圆角。
collisionAvoidanceCollisionAvoidance-决定弹出层定位时如何处理碰撞。 `side` 控制首选放置轴(`top`/`bottom` 或 `left`/`right`)上的溢出处理: - `'flip'`:能放下时保持请求的边缘,否则尝试相对的一侧(`top` 与 `bottom`,或 `left` 与 `right`)。 - `'shift'`:不改变边缘,保持请求的边缘并在裁剪边界内移动弹出层,使其保持可见。 - `'none'`:不修正边缘轴上的溢出。 `align` 控制对齐轴(`start`/`center`/`end`)上的溢出处理: - `'flip'`:保持边缘,但当请求的对齐方式溢出时互换 `start` 与 `end`。 - `'shift'`:保持边缘和请求的对齐方式,然后沿对齐轴微调弹出层位置以适应空间。 - `'none'`:不修正对齐轴上的溢出。 `fallbackAxisSide` 控制首选轴无法容纳时在垂直轴上的回退行为: - `'start'`:允许回退到垂直轴,并优先尝试逻辑起始侧(LTR 下先 `top` 后 `bottom`,或先 `left` 后 `right`)。 - `'end'`:允许回退到垂直轴,并优先尝试逻辑结束侧(LTR 下先 `bottom` 后 `top`,或先 `right` 后 `left`)。 - `'none'`:不回退到垂直轴。 当 `side` 为 `'shift'` 时,显式设置 `align` 只支持 `'shift'` 或 `'none'`。省略 `align` 时默认为 `'flip'`。
collisionBoundaryBoundary-限定弹出层活动区域的元素或矩形。
collisionPaddingPadding-与碰撞边界边缘之间保持的额外间距。
disableAnchorTrackingboolean-是否禁止弹出层跟随定位锚点的布局变化。
positionMethod"absolute" | "fixed"-使用哪个 CSS `position` 属性。
side"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"-弹出层对齐在锚点元素的哪一侧。为避免碰撞可能会自动改变。
sideOffsetnumber | OffsetFunction-锚点元素与弹出层之间的距离(像素)。也可以传入一个返回距离的函数,该函数可读取锚点元素与定位层元素的尺寸,以及它们的边缘和对齐方式。 该函数接收一个 `data` 对象参数,包含以下属性: - `data.anchor`:锚点元素的尺寸,包含 `width` 和 `height` 属性。 - `data.positioner`:定位层元素的尺寸,包含 `width` 和 `height` 属性。 - `data.side`:定位层对齐在锚点元素的哪一侧。 - `data.align`:定位层相对指定边缘的对齐方式。
stickyboolean-当锚点元素滚出视口后,是否让弹出层保持在视口内。
classNamestring-通过 `cn()` 合并的额外 CSS 类名。
renderReactNode-替换触发元素,同时保留 Select 的行为。
size"xs" | "sm" | "base" | "lg""base"选择器触发器的尺寸。与 Input 组件的尺寸一致。
labelReactNode-选择器的标签内容。提供后会在选择器上方启用带可见标签的 Field 包裹层。若需要无障碍支持但不想显示标签,请改用 `aria-label`。
hideLabelboolean--
placeholderstring-未选中任何值时显示的占位符文字。
loadingboolean-为 `true` 时,用骨架加载指示器代替选中值显示。
disabledboolean-选择器是否禁用。
requiredboolean-选择器是否必填。为 `false` 时显示“(optional)”文字。
labelTooltipReactNode-通过信息图标显示在标签旁的工具提示内容。
valueT-当前选中的值(受控模式)。
childrenReactNode-在下拉菜单中渲染的 `Select.Option` 元素。
descriptionReactNode-显示在选择器下方的辅助说明文字。
errorstring | object-错误信息字符串,或带 `match` 键的校验错误对象。
onValueChange(value: T) => void-选中项变化时的回调
defaultValueT-非受控模式下的初始值
renderValue(value: T) => ReactNode-返回 ReactNode 的函数,用于格式化触发元素中显示的选中值。使用对象值时必填。空状态请使用 `placeholder`。
itemsRecord<string, string> | Array<{ label: ReactNode; value: T }>-弹出层中渲染的选项数据结构。接受普通对象映射(`{ key: "Label" }`)或 `{ label, value }` 数组,用于对象或复杂值。
isItemEqualToValue(item: T, value: T) => boolean-用于比较选项的自定义相等判断函数。值为对象时必填,因为对象引用(`===`)在不同渲染之间无法匹配。

Select.Option

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

Select.Group

使用具有无障碍语义的 role="group" 将相关选项组合在一起。与 Select.GroupLabel 搭配使用 可提供可见标题。

Select.GroupLabel

Select.Group 的可见标题。为了无障碍,它会自动与父级分组关联。

Select.Separator

选项分组之间的视觉分隔线。渲染时带有 role="separator"。