Skip to content
Lenso UI

Autocomplete 自动完成

结合选择与过滤,让用户从选项列表中搜索并选择

用法

import { Autocomplete, useFilter } from "@lenso/ui";

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";
export const states = [  { id: "florida", name: "Florida" },  { id: "delaware", name: "Delaware" },  { id: "california", name: "California" },  { id: "texas", name: "Texas" },  { id: "new-york", name: "New York" },  { id: "washington", name: "Washington" },];export default function Default() {  return (    <NativeAutocomplete      items={states}      label="States to Visit"      placeholder="Select states"      multiple      chips      hideClear    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

组件结构

import {Autocomplete, Label, Description, SearchField, ListBox} from "@lenso/ui";
export default () => (  <Autocomplete>    <Label />    <Autocomplete.Trigger>      <Autocomplete.Value />      <Autocomplete.ClearButton />      <Autocomplete.Indicator />    </Autocomplete.Trigger>    <Description />    <Autocomplete.Popover>      <Autocomplete.Filter>        <SearchField>          <SearchField.Group>            <SearchField.SearchIcon />            <SearchField.Input />          </SearchField.Group>        </SearchField>        <ListBox>          <ListBox.Item>            <Label />            <ListBox.ItemIndicator />          </ListBox.Item>        </ListBox>      </Autocomplete.Filter>    </Autocomplete.Popover>  </Autocomplete>);

示例

变体

Autocomplete 组件支持两种视觉变体:

  • primary(默认)- 标准样式带阴影,适用于大多数场景
  • secondary - 低强调变体无阴影,适用于 Surface 组件内

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";const items = [1, 2, 3, 4].map((number) => ({ id: `option${number}`, name: `Option ${number}` }));export function Variants() {  return (    <div {...stylex.props(styles.section)}>      <section {...stylex.props(styles.stack)}>        <h3>Single Select Variants</h3>        <NativeAutocomplete items={items} label="Primary variant" variant="primary" />        <NativeAutocomplete items={items} label="Secondary variant" variant="secondary" />      </section>      <section {...stylex.props(styles.stack)}>        <h3>Multiple Select Variants</h3>        <NativeAutocomplete          items={items}          label="Primary variant"          variant="primary"          placeholder="Select multiple"          multiple          chips        />        <NativeAutocomplete          items={items}          label="Secondary variant"          variant="secondary"          placeholder="Select multiple"          multiple          chips        />      </section>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

宽度充满

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Surface } from "@lenso/ui";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";export function FullWidth() {  return (    <Surface xstyle={[styles.surface, styles.fullSurface]}>      <NativeAutocomplete        items={states}        label="State"        variant="secondary"        fullWidth        searchLabel="Search states"      />    </Surface>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

带描述

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";import { states } from "./default";export function WithDescription() {  return (    <NativeAutocomplete      items={states}      label="State"      searchLabel="Search states"      searchPlaceholder="Search 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. SPDX-License-Identifier: Apache-2.0 */import { Button } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles, type Option } from "./_native";import { states } from "./default";
const countries = [  { id: "usa", name: "United States" },  { id: "canada", name: "Canada" },  { id: "mexico", name: "Mexico" },  { id: "uk", name: "United Kingdom" },  { id: "france", name: "France" },  { id: "germany", name: "Germany" },];export function Required() {  const [state, setState] = useState<Option | Option[] | null>(null);  const [country, setCountry] = useState<Option | Option[] | null>(null);  const [submitted, setSubmitted] = useState(false);  return (    <form      noValidate      {...stylex.props(styles.stack)}      onSubmit={(event) => {        event.preventDefault();        setSubmitted(true);        const data = new FormData(event.currentTarget);        if (data.get("state") && data.get("country")) {          alert("Form submitted successfully!");          return;        }        const fields =          event.currentTarget.querySelectorAll<HTMLButtonElement>('button[role="combobox"]');        const firstInvalid = fields[data.get("state") ? 1 : 0];        requestAnimationFrame(() => {          if (firstInvalid?.isConnected) firstInvalid.focus();        });      }}    >      <NativeAutocomplete        items={states}        label="State"        name="state"        required        value={state}        onValueChange={setState}        invalid={submitted && !state}        error={submitted && !state ? "Please select a state." : undefined}        searchLabel="Search states"      />      <NativeAutocomplete        items={countries}        label="Country"        name="country"        required        value={country}        onValueChange={setCountry}        invalid={submitted && !country}        error={submitted && !country ? "Please select a country." : undefined}        placeholder="Select a country"        searchLabel="Search countries"      />      <Button type="submit">Submit</Button>    </form>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

禁用

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";const countries = [  { id: "argentina", name: "Argentina" },  { id: "venezuela", name: "Venezuela" },  { id: "japan", name: "Japan" },  { id: "france", name: "France" },  { id: "italy", name: "Italy" },  { id: "spain", name: "Spain" },];export function Disabled() {  return (    <div {...stylex.props(styles.stack)}>      <NativeAutocomplete        items={states}        label="State"        disabled        defaultValue={states.find((item) => item.id === "california")}      />      <NativeAutocomplete        items={countries}        label="Countries to Visit"        placeholder="Select countries"        disabled        multiple        defaultValue={countries.filter((item) =>          ["argentina", "japan", "france"].includes(item.id),        )}      />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

含禁用选项

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const animals = [  { id: "dog", name: "Dog" },  { id: "cat", name: "Cat", disabled: true },  { id: "bird", name: "Bird" },  { id: "kangaroo", name: "Kangaroo", disabled: true },  { id: "elephant", name: "Elephant" },  { id: "tiger", name: "Tiger" },];export function WithDisabledOptions() {  return (    <NativeAutocomplete      items={animals}      label="Animal"      placeholder="Select an animal"      searchLabel="Search animals"      searchPlaceholder="Search animals..."    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

允许空选项

allowsEmptyCollection 属性允许集合为空时仍可使用自动完成,适用于列表初始为空或全部被过滤掉的场景。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";export function AllowsEmptyCollection() {  return (    <NativeAutocomplete      items={[]}      label="State"      searchLabel="Search states"      searchPlaceholder="Search states..."    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

分组选项

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Autocomplete } from "@lenso/ui";import { useId, useState } from "react";import { NativeAutocomplete } from "./_native";
const groups = [  {    name: "North America",    items: [      { id: "usa", name: "United States" },      { id: "canada", name: "Canada" },      { id: "mexico", name: "Mexico" },    ],  },  {    name: "Europe",    items: [      { id: "uk", name: "United Kingdom" },      { id: "france", name: "France" },      { id: "germany", name: "Germany" },      { id: "spain", name: "Spain" },      { id: "italy", name: "Italy" },    ],  },  {    name: "Asia",    items: [      { id: "japan", name: "Japan" },      { id: "china", name: "China" },      { id: "india", name: "India" },      { id: "south-korea", name: "South Korea" },    ],  },];export function WithSections() {  const id = useId();  const { contains } = Autocomplete.useFilter({ sensitivity: "base" });  const [query, setQuery] = useState("");  const visibleGroups = groups    .map((group) => ({      ...group,      items: group.items.filter((item) => contains(item.name, query)),    }))    .filter((group) => group.items.length);  return (    <NativeAutocomplete      items={groups.flatMap((group) => group.items)}      filteredItems={visibleGroups.flatMap((group) => group.items)}      label="Country"      placeholder="Select a country"      searchLabel="Search countries"      searchPlaceholder="Search countries..."      inputValue={query}      onInputValueChange={setQuery}      list={        <Autocomplete.List>          {visibleGroups.map((group, index) => (            <Autocomplete.Group key={group.name} aria-labelledby={`${id}-${index}`}>              {index > 0 && <Autocomplete.Separator />}              <Autocomplete.GroupLabel id={`${id}-${index}`}>{group.name}</Autocomplete.GroupLabel>              {group.items.map((item) => (                <Autocomplete.Item key={item.id} value={item}>                  {item.name}                  <Autocomplete.ItemIndicator />                </Autocomplete.Item>              ))}            </Autocomplete.Group>          ))}        </Autocomplete.List>      }    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

多选

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";import { controlledStates } from "./controlled";export function MultipleSelect() {  return (    <NativeAutocomplete      items={controlledStates}      label="States"      placeholder="Select states"      multiple      chips    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

在多选模式下,直接使用 <Autocomplete.Value /> 会将每个选中项在 ListBox.Item 中的 children 复制到触发器中,并以符合当前语言环境的分隔符连接。仅包含文本的选项会显示为纯文本,但复选框、头像等可见内容会随每个选中项各出现一次;函数形式的 children 也始终以 isSelected: false 调用。

如需以纯文本显示选中值,请使用 selectedText 渲染属性,它会将每个选中项的 textValue 连接为一个字符串:

<Autocomplete.Value>  {({isPlaceholder, defaultChildren, selectedText}) =>    isPlaceholder ? defaultChildren : selectedText  }</Autocomplete.Value>

如需可移除标签等更丰富的布局,请参考上方示例,基于 state.selectedItems 自行渲染内容。

受控组件

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles, type Option } from "./_native";export const controlledStates = [  { id: "california", name: "California" },  { id: "texas", name: "Texas" },  { id: "florida", name: "Florida" },  { id: "new-york", name: "New York" },  { id: "illinois", name: "Illinois" },  { id: "pennsylvania", name: "Pennsylvania" },];export function Controlled() {  const [state, setState] = useState<Option | Option[] | null>(controlledStates[0] ?? null);  return (    <div {...stylex.props(styles.stack)}>      <NativeAutocomplete        items={controlledStates}        label="State (controlled)"        placeholder="Select a state"        searchLabel="Search states"        value={state}        onValueChange={setState}      />      <p {...stylex.props(styles.muted)}>        Selected: {state && !Array.isArray(state) ? state.name : "None"}      </p>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

受控多选

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles, type Option } from "./_native";import { controlledStates } from "./controlled";export function ControlledMultiple() {  const [selected, setSelected] = useState<Option | Option[] | null>(() =>    controlledStates.slice(0, 2),  );  return (    <div {...stylex.props(styles.stack)}>      <NativeAutocomplete        items={controlledStates}        label="States"        placeholder="Select states"        multiple        value={selected}        onValueChange={setSelected}        searchLabel="Search states"      />      <p {...stylex.props(styles.muted)}>        Selected:{" "}        {Array.isArray(selected) && selected.length          ? selected.map((item) => item.id).join(", ")          : "None"}      </p>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

受控展开状态

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Button } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";export function ControlledOpenState() {  const [open, setOpen] = useState(false);  return (    <div {...stylex.props(styles.stack)}>      <NativeAutocomplete        items={states}        label="State"        searchLabel="Search states"        open={open}        onOpenChange={setOpen}      />      <Button onClick={() => setOpen(!open)}>{open ? "Close" : "Open"} Autocomplete</Button>      <p {...stylex.props(styles.muted)}>Autocomplete is {open ? "open" : "closed"}</p>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

异步搜索

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { useEffect, useState } from "react";import { NativeAutocomplete, type Option } from "./_native";
interface ResponseData {  results: { name: string }[];}export function AsynchronousFiltering() {  const [query, setQuery] = useState("");  const [result, setResult] = useState<{ query: string; items: Option[]; error?: string } | null>(    null,  );  useEffect(() => {    const controller = new AbortController();    const load = async () => {      try {        const response = await fetch(          `https://swapi.py4e.com/api/people/?search=${encodeURIComponent(query)}`,          { signal: controller.signal },        );        if (!response.ok) throw new Error("Could not load characters");        const data = (await response.json()) as ResponseData;        if (!controller.signal.aborted)          setResult({            query,            items: data.results.map((item) => ({ id: item.name, name: item.name })),          });      } catch (error) {        if (!controller.signal.aborted)          setResult({            query,            items: [],            error: error instanceof Error ? error.message : "Could not load characters",          });      }    };    void load();    return () => controller.abort();  }, [query]);  const loading = result?.query !== query;  return (    <NativeAutocomplete      items={result?.items ?? []}      filteredItems={loading ? [] : (result?.items ?? [])}      label="Search a Star Wars characters"      placeholder="Search..."      searchLabel="Search characters"      searchPlaceholder="Search characters..."      inputValue={query}      onInputValueChange={setQuery}      loading={loading}      emptyText={result?.error || "No results found"}    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义指示器

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { ChevronsExpandVertical } from "@gravity-ui/icons";import { NativeAutocomplete } from "./_native";import { states } from "./default";export function CustomIndicator() {  return (    <NativeAutocomplete      items={states}      label="State"      searchLabel="Search states"      indicator={<ChevronsExpandVertical width={12} height={12} />}    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义展示值

可使用 render props 自定义显示值:

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";
const currencies = [  { code: "USD", id: "usd", name: "US Dollar", symbol: "$" },  { code: "EUR", id: "eur", name: "Euro", symbol: "€" },  { code: "GBP", id: "gbp", name: "British Pound", symbol: "£" },  { code: "JPY", id: "jpy", name: "Japanese Yen", symbol: "¥" },  { code: "CHF", id: "chf", name: "Swiss Franc", symbol: "₣" },].map((currency) => ({ ...currency, searchText: `${currency.code} ${currency.name}` }));export function CustomValue() {  return (    <NativeAutocomplete      items={currencies}      label="Currency"      placeholder="Select a currency"      hideClear      defaultValue={currencies[0]}      searchLabel="Search currencies"      searchPlaceholder="Search currencies..."      renderItem={(item) => {        const currency = currencies.find((entry) => entry.id === item.id);        return (          <span {...stylex.props(styles.details)}>            <span>{currency?.code}</span>            <span {...stylex.props(styles.muted)}>{item.name}</span>          </span>        );      }}      renderValue={(item) => {        const currency = currencies.find((entry) => entry.id === item.id);        return currency ? (          <span {...stylex.props(styles.row)}>            <strong>{currency.symbol}</strong>            <span>{currency.code}</span>            <span {...stylex.props(styles.muted)}>{currency.name}</span>          </span>        ) : (          item.name        );      }}    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

表面样式

在 Surface 组件内使用时,请使用 variant="secondary" 以应用适合 Surface 背景的低强调变体。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Surface } from "@lenso/ui";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";export function OnSurface() {  return (    <Surface xstyle={styles.surface}>      <NativeAutocomplete        items={states}        label="State"        variant="secondary"        fullWidth        searchLabel="Search states"      />    </Surface>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

虚拟滚动

Autocomplete 通过 Virtualizer 支持虚拟化,仅渲染视口内可见行以高效处理大数据集。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Autocomplete } from "@lenso/ui";import { useRef, useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, OptionContent, type Option } from "./_native";
const firstNames = [  "Emma",  "Liam",  "Olivia",  "Noah",  "Ava",  "James",  "Sophia",  "Oliver",  "Isabella",  "Lucas",  "Mia",  "Ethan",  "Charlotte",  "Mason",  "Amelia",  "Logan",  "Harper",  "Alexander",  "Ella",  "Benjamin",];const lastNames = [  "Smith",  "Johnson",  "Williams",  "Brown",  "Jones",  "Garcia",  "Miller",  "Davis",  "Rodriguez",  "Martinez",  "Anderson",  "Taylor",  "Thomas",  "Jackson",  "White",  "Harris",  "Clark",  "Lewis",  "Robinson",  "Walker",];const allUsers: Option[] = Array.from({ length: 1000 }, (_, index) => {  const first = firstNames[index % firstNames.length] ?? "";  const last = lastNames[Math.floor(index / firstNames.length) % lastNames.length] ?? "";  return {    id: String(index + 1),    name: `${first} ${last}`,    email: `${first.toLowerCase()}.${last.toLowerCase()}@acme.com`,  };});const virtualStyles = stylex.create({  field: { width: 300 },  list: { height: 300, overflowY: "auto", position: "relative", padding: 0 },  row: { height: 50, boxSizing: "border-box" },  spacer: (height: number) => ({ height }),});export function Virtualization() {  const { contains } = Autocomplete.useFilter({ sensitivity: "base" });  const [query, setQuery] = useState("");  const [scrollTop, setScrollTop] = useState(0);  const listRef = useRef<HTMLDivElement>(null);  const filtered = allUsers.filter(    (user) => contains(user.name, query) || contains(user.email ?? "", query),  );  const start = Math.max(0, Math.floor(scrollTop / 50) - 3);  const end = Math.min(filtered.length, start + 14);  const onHighlight = (item: Option | undefined) => {    const index = filtered.findIndex((user) => user.id === item?.id);    const element = listRef.current;    if (index < 0 || !element) return;    const top = index * 50;    if (top < element.scrollTop) element.scrollTop = top;    else if (top + 50 > element.scrollTop + 300) element.scrollTop = top - 250;    setScrollTop(element.scrollTop);  };  return (    <NativeAutocomplete      items={allUsers}      xstyle={virtualStyles.field}      filteredItems={filtered}      virtualized      onItemHighlighted={onHighlight}      label="User"      placeholder="Select a user"      searchLabel="Search users"      searchPlaceholder="Search users..."      inputValue={query}      onInputValueChange={(next) => {        setQuery(next);        setScrollTop(0);        if (listRef.current) listRef.current.scrollTop = 0;      }}      list={        <Autocomplete.List          ref={listRef}          xstyle={virtualStyles.list}          onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}        >          <div aria-hidden="true" {...stylex.props(virtualStyles.spacer(start * 50))} />          {filtered.slice(start, end).map((item, offset) => (            <Autocomplete.Item              key={item.id}              value={item}              index={start + offset}              aria-setsize={filtered.length}              aria-posinset={start + offset + 1}              xstyle={virtualStyles.row}            >              <OptionContent item={item} />              <Autocomplete.ItemIndicator />            </Autocomplete.Item>          ))}          <div            aria-hidden="true"            {...stylex.props(virtualStyles.spacer((filtered.length - end) * 50))}          />        </Autocomplete.List>      }    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

高级示例

用户选择

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, OptionAvatar, styles } from "./_native";export const users = [  {    avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",    email: "[email protected]",    fallback: "B",    id: "1",    name: "Bob",  },  {    avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",    email: "[email protected]",    fallback: "F",    id: "2",    name: "Fred",  },  {    avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",    email: "[email protected]",    fallback: "M",    id: "3",    name: "Martha",  },  {    avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",    email: "[email protected]",    fallback: "J",    id: "4",    name: "John",  },  {    avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",    email: "[email protected]",    fallback: "J",    id: "5",    name: "Jane",  },];export function UserSelection() {  return (    <NativeAutocomplete      items={users}      label="User"      placeholder="Select a user"      searchLabel="Search users"      searchPlaceholder="Search users..."      renderValue={(item) => (        <span {...stylex.props(styles.row)}>          <OptionAvatar item={item} small />          {item.name}        </span>      )}    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

用户多选

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";import { users } from "./user-selection";export function UserSelectionMultiple() {  return (    <NativeAutocomplete      items={users}      label="Users"      placeholder="Select your teammates"      multiple      chips      searchLabel="Search users"      searchPlaceholder="Search users..."    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

地点搜索

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Autocomplete } from "@lenso/ui";import { useEffect, useState } from "react";import { NativeAutocomplete } from "./_native";
const cities = [  { country: "USA", name: "New York" },  { country: "USA", name: "Los Angeles" },  { country: "USA", name: "Chicago" },  { country: "UK", name: "London" },  { country: "France", name: "Paris" },  { country: "Japan", name: "Tokyo" },  { country: "Australia", name: "Sydney" },  { country: "Canada", name: "Toronto" },  { country: "Germany", name: "Berlin" },  { country: "Spain", name: "Madrid" },].map((city) => ({ ...city, id: city.name }));
export function LocationSearch() {  const { contains } = Autocomplete.useFilter({ sensitivity: "base" });  const [query, setQuery] = useState("");  const [settledQuery, setSettledQuery] = useState("");  useEffect(() => {    const timeout = setTimeout(() => setSettledQuery(query), 300);    return () => clearTimeout(timeout);  }, [query]);  const loading = query !== settledQuery;  const results = cities.filter((city) => contains(city.name, settledQuery));  return (    <NativeAutocomplete      items={cities}      label="City"      placeholder="Search for a city"      searchLabel="Search cities"      searchPlaceholder="Search cities..."      inputValue={query}      onInputValueChange={setQuery}      filteredItems={loading ? [] : results}      loading={loading}      emptyText="No cities found"    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

标签组选择

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const tags = [  { id: "react", name: "React" },  { id: "typescript", name: "TypeScript" },  { id: "javascript", name: "JavaScript" },  { id: "nodejs", name: "Node.js" },  { id: "python", name: "Python" },  { id: "vue", name: "Vue" },  { id: "angular", name: "Angular" },  { id: "nextjs", name: "Next.js" },];export function TagGroupSelection() {  return (    <NativeAutocomplete      items={tags}      label="Tags"      placeholder="Select tags"      multiple      chips      searchLabel="Search tags"      searchPlaceholder="Search tags..."      emptyText="No tags found"    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

邮件收件人

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const emails = [  { email: "[email protected]", id: "[email protected]", name: "Alice Johnson" },  { email: "[email protected]", id: "[email protected]", name: "Bob Smith" },  { email: "[email protected]", id: "[email protected]", name: "Charlie Brown" },  { email: "[email protected]", id: "[email protected]", name: "Diana Prince" },  { email: "[email protected]", id: "[email protected]", name: "Eve Wilson" },];export function EmailRecipients() {  return (    <NativeAutocomplete      items={emails}      label="To"      placeholder="Add recipients"      multiple      chips      chipText={(item) => item.email}      searchLabel="Search emails"      searchPlaceholder="Search emails..."      emptyText="No recipients found"    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义样式

Tailwind CSS

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const teammates = [  { id: "sarah", name: "Sarah Chen", role: "Product Design" },  { id: "marcus", name: "Marcus Lee", role: "Engineering" },  { id: "priya", name: "Priya Patel", role: "Data Science" },  { id: "jordan", name: "Jordan Kim", role: "Customer Success" },  { id: "alex", name: "Alex Rivera", role: "Marketing" },];export function CustomStyles() {  return (    <NativeAutocomplete      items={teammates}      label="Assignees"      description="People who will be notified when this task updates."      placeholder="Search teammates..."      searchLabel="Search by name or role"      searchPlaceholder="Search by name or role..."      multiple      chips      custom      emptyText="No teammates found"    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

全局 CSS

可使用 @layer components 指令自定义 Autocomplete 组件类。

了解更多。

@layer components {  .autocomplete {    @apply flex flex-col gap-1;  }
  .autocomplete__trigger {    @apply rounded-lg border border-border bg-surface p-2;  }
  .autocomplete__value {    @apply text-current;  }
  .autocomplete__clear-button {    @apply text-muted hover:text-foreground;  }
  .autocomplete__indicator {    @apply text-muted;  }
  .autocomplete__popover {    @apply rounded-lg border border-border bg-surface p-2;  }}

样式参考

HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。

CSS 类

Autocomplete 组件使用以下 CSS 类(查看源码样式):

基础类 [!toc]

  • .autocomplete - 自动完成根容器
  • .autocomplete__trigger - 触发自动完成的按钮
  • .autocomplete__value - 显示的值或占位符
  • .autocomplete__clear-button - 清除已选值的按钮
  • .autocomplete__indicator - 下拉指示图标
  • .autocomplete__popover - 弹出层容器
  • .autocomplete__filter - 过滤包装器

变体类 [!toc]

  • .autocomplete--primary - 带阴影的主变体(默认)
  • .autocomplete--secondary - 无阴影的次变体,适用于 Surface 内

状态类 [!toc]

  • .autocomplete[data-invalid="true"] - 无效状态
  • .autocomplete__trigger[data-focus-visible="true"] - 触发器聚焦状态
  • .autocomplete__trigger[data-disabled="true"] - 触发器禁用状态
  • .autocomplete__value[data-placeholder="true"] - 占位符状态
  • .autocomplete__clear-button[data-empty="true"] - 无选择时隐藏清除按钮
  • .autocomplete__indicator[data-open="true"] - 打开时的指示器状态

交互状态

组件同时支持 CSS 伪类与 data 属性:

  • Hover:触发器上的 :hover 或 [data-hovered="true"]
  • Focus:触发器上的 :focus-visible 或 [data-focus-visible="true"]
  • Disabled:Autocomplete 上的 :disabled 或 [data-disabled="true"]
  • Open:指示器上的 [data-open="true"]

API 参考

Autocomplete

Prop类型默认值描述
placeholderstring'Select an item'为空时显示的占位文本
selectionMode"single" | "multiple""single"启用单选或多选
allowsEmptyCollectionbooleanfalse是否允许空集合;为 true 时无选项也可使用
isOpenboolean-弹出层打开状态(受控)
defaultOpenboolean-弹出层默认打开状态(非受控)
onOpenChange(isOpen: boolean) => void-打开状态变化时的回调
disabledKeysIterable<Key>-禁用项的 key
isDisabledboolean-是否禁用
valueKey | Key[] | null-当前值(受控)
defaultValueKey | Key[] | null-默认值(非受控)
onChange(value: Key | Key[] | null) => void-值变化时的回调
isRequiredboolean-是否必填
isInvalidboolean-值是否无效
namestring-提交 HTML 表单时使用的名称
fullWidthbooleanfalse是否占满容器宽度
variant"primary" | "secondary""primary"视觉变体。primary 为默认带阴影样式;secondary 为低强调无阴影,适用于 Surface 内
classNamestring-附加 CSS 类
childrenReactNode | RenderFunction-内容或 render 函数

Autocomplete.Trigger

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReactNode | RenderFunction-触发器内容或 render 函数

Autocomplete.Value

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReactNode | RenderFunction-值内容或 render 函数

Autocomplete.Indicator

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReactNode-自定义指示器内容

Autocomplete.ClearButton

Prop类型默认值描述
classNamestring-附加 CSS 类
onClick(e: MouseEvent) => void-点击按钮时的回调
refRefObject<HTMLButtonElement>-清除按钮元素的 ref

Autocomplete.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"弹出层相对触发器的位置
classNamestring-附加 CSS 类
childrenReactNode-子内容

Autocomplete.Filter

Prop类型默认值描述
filter(text: string, input: string) => boolean-自定义过滤函数
inputValuestring-受控输入值
onInputChange(value: string) => void-输入值变化时的回调
childrenReactNode-过滤内容(SearchField 与 ListBox)

useFilter Hook

React Aria 的 useFilter hook 提供自动完成过滤函数。

import {useFilter} from "@lenso/ui";
const {contains} = useFilter({sensitivity: "base"});
<Autocomplete.Filter filter={contains}>  <SearchField>...</SearchField>  <ListBox>...</ListBox></Autocomplete.Filter>

Options:

Option类型默认值描述
sensitivity"base" | "accent" | "case" | "variant""base"匹配的 locale 敏感度

Returns:

Function类型描述
contains(string: string, substring: string) => boolean判断字符串是否包含子串
startsWith(string: string, substring: string) => boolean判断字符串是否以子串开头
endsWith(string: string, substring: string) => boolean判断字符串是否以子串结尾

Render Props

使用 Autocomplete.Value 的 render 函数时,提供以下值:

Prop类型描述
defaultChildrenReactNode默认渲染的值。多选模式下为每个选中项 children 的副本,以符合当前语言环境的分隔符连接
isPlaceholderboolean是否为占位符
stateSelectState自动完成的状态。如需获取选中的集合节点,请使用 state.selectedItems
selectedItems(T | null)[]当前选中项的值
selectedTextstring选中项的 textValue,以符合当前语言环境的分隔符连接

无障碍

Autocomplete 组件实现带过滤的 ARIA select 模式,提供:

  • 完整键盘导航支持
  • 选择变化的屏幕阅读器播报
  • 正确的焦点管理
  • 禁用状态支持
  • 带过滤的搜索功能
  • HTML 表单集成

更多信息请参阅 React Aria Select 文档。

相关组件