import { useState } from "react";
import { CommandPalette, Button } from "@lark-ui/core";
export function CommandPaletteBasicDemo() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const handleSelect = (item: CommandItem) => {
setSelectedItem(item.title);
setOpen(false);
setSearch("");
};
// Filter groups based on search
const filteredGroups = filterGroupsWithItems(sampleGroups, search);
return (
<div className="flex flex-col items-start gap-4">
<Button onClick={() => setOpen(true)}>打开命令面板</Button>
{selectedItem && (
<p className="text-sm text-lark-subtle">
最近选择: <span className="text-lark-default">{selectedItem}</span>
</p>
)}
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={filteredGroups}
value={search}
onValueChange={setSearch}
itemToStringValue={(group) => group.label}
onSelect={(item, { newTab }) => {
console.log("Selected:", item.title, newTab ? "(new tab)" : "");
handleSelect(item);
}}
getSelectableItems={getSelectableItems}
>
<CommandPalette.Input placeholder="输入命令或搜索内容…" />
<CommandPalette.List>
<CommandPalette.Results>
{(group: CommandGroup) => (
<CommandPalette.Group key={group.id} items={group.items}>
<CommandPalette.GroupLabel>
{group.label}
</CommandPalette.GroupLabel>
<CommandPalette.Items>
{(item: CommandItem) => (
<CommandPalette.Item
key={item.id}
value={item}
onClick={() => handleSelect(item)}
>
<span className="flex items-center gap-3">
{item.icon && (
<span className="text-lark-subtle">{item.icon}</span>
)}
<span>{item.title}</span>
</span>
</CommandPalette.Item>
)}
</CommandPalette.Items>
</CommandPalette.Group>
)}
</CommandPalette.Results>
<CommandPalette.Empty>未找到命令</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span className="flex items-center gap-2">
<kbd className="rounded border border-lark-hairline bg-lark-base px-1.5 py-0.5 text-[10px]">
↑↓
</kbd>
<span>导航</span>
</span>
<span className="flex items-center gap-2">
<kbd className="rounded border border-lark-hairline bg-lark-base px-1.5 py-0.5 text-[10px]">
↵
</kbd>
<span>选择</span>
</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</div>
);
}安装
统一导出
import { CommandPalette } from "@lark-ui/core";按需导出
import { CommandPalette } from "@lark-ui/core/components/command-palette";用法
CommandPalette 是基于 Base UI 的 Autocomplete 原语构建的复合组件。它为命令面板界面提供无障碍的键盘导航和可自定义的样式。
import { useState } from "react";
import { CommandPalette } from "@lark-ui/core";
interface Item {
id: string;
title: string;
}
const items: Item[] = [
{ id: "1", title: "Create Project" },
{ id: "2", title: "Open Settings" },
];
export default function Example() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
return (
<>
<button onClick={() => setOpen(true)}>Open</button>
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={items}
value={search}
onValueChange={setSearch}
itemToStringValue={(item) => item.title}
getSelectableItems={(items) => items}
>
<CommandPalette.Input placeholder="Search..." />
<CommandPalette.List>
<CommandPalette.Results>
{(item) => (
<CommandPalette.Item
key={item.id}
value={item}
onClick={() => setOpen(false)}
>
{item.title}
</CommandPalette.Item>
)}
</CommandPalette.Results>
<CommandPalette.Empty>No results</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</>
);
}键盘导航
内置键盘导航,无需额外配置:
↑+↓在选项之间移动高亮
Enter选中当前高亮的选项
⌘/Ctrl+Enter以 newTab: true 方式选中
Escape关闭弹层
示例
带分组的选项
用标签把相关命令归为一组。
import { useState } from "react";
import { CommandPalette, Button } from "@lark-ui/core";
export function CommandPaletteBasicDemo() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const handleSelect = (item: CommandItem) => {
setSelectedItem(item.title);
setOpen(false);
setSearch("");
};
// Filter groups based on search
const filteredGroups = filterGroupsWithItems(sampleGroups, search);
return (
<div className="flex flex-col items-start gap-4">
<Button onClick={() => setOpen(true)}>打开命令面板</Button>
{selectedItem && (
<p className="text-sm text-lark-subtle">
最近选择: <span className="text-lark-default">{selectedItem}</span>
</p>
)}
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={filteredGroups}
value={search}
onValueChange={setSearch}
itemToStringValue={(group) => group.label}
onSelect={(item, { newTab }) => {
console.log("Selected:", item.title, newTab ? "(new tab)" : "");
handleSelect(item);
}}
getSelectableItems={getSelectableItems}
>
<CommandPalette.Input placeholder="输入命令或搜索内容…" />
<CommandPalette.List>
<CommandPalette.Results>
{(group: CommandGroup) => (
<CommandPalette.Group key={group.id} items={group.items}>
<CommandPalette.GroupLabel>
{group.label}
</CommandPalette.GroupLabel>
<CommandPalette.Items>
{(item: CommandItem) => (
<CommandPalette.Item
key={item.id}
value={item}
onClick={() => handleSelect(item)}
>
<span className="flex items-center gap-3">
{item.icon && (
<span className="text-lark-subtle">{item.icon}</span>
)}
<span>{item.title}</span>
</span>
</CommandPalette.Item>
)}
</CommandPalette.Items>
</CommandPalette.Group>
)}
</CommandPalette.Results>
<CommandPalette.Empty>未找到命令</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span className="flex items-center gap-2">
<kbd className="rounded border border-lark-hairline bg-lark-base px-1.5 py-0.5 text-[10px]">
↑↓
</kbd>
<span>导航</span>
</span>
<span className="flex items-center gap-2">
<kbd className="rounded border border-lark-hairline bg-lark-base px-1.5 py-0.5 text-[10px]">
↵
</kbd>
<span>选择</span>
</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</div>
);
}简单扁平列表
对于较简单的场景,可直接使用不分组的扁平选项数组。
import { useState } from "react";
import { CommandPalette, Button } from "@lark-ui/core";
export function CommandPaletteSimpleDemo() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
return (
<div>
<Button onClick={() => setOpen(true)}>打开简易面板</Button>
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={simpleItems}
value={search}
onValueChange={setSearch}
itemToStringValue={(item) => item.title}
onSelect={(item) => {
console.log("Selected:", item.title);
setOpen(false);
}}
getSelectableItems={(items) => items}
>
<CommandPalette.Input placeholder="搜索操作…" />
<CommandPalette.List>
<CommandPalette.Results>
{(item: SimpleItem) => (
<CommandPalette.Item
key={item.id}
value={item}
onClick={() => {
console.log("Clicked:", item.title);
setOpen(false);
}}
>
{item.title}
</CommandPalette.Item>
)}
</CommandPalette.Results>
<CommandPalette.Empty>未找到操作</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</div>
);
}加载状态
获取结果期间显示加载动画。
import { useState } from "react";
import { CommandPalette, Button } from "@lark-ui/core";
// With loading state
export function CommandPaletteLoadingDemo() {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState("");
const handleOpen = () => {
setOpen(true);
setLoading(true);
// Simulate loading
setTimeout(() => setLoading(false), 1500);
};
// Filter groups based on search
const filteredGroups = filterGroupsWithItems(sampleGroups, search);
return (
<div>
<Button onClick={handleOpen}>打开(带加载状态)</Button>
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={loading ? [] : filteredGroups}
value={search}
onValueChange={setSearch}
itemToStringValue={(group) => group.label}
getSelectableItems={getSelectableItems}
>
<CommandPalette.Input placeholder="搜索…" />
<CommandPalette.List>
{loading ? (
<CommandPalette.Loading />
) : (
<>
<CommandPalette.Results>
{(group: CommandGroup) => (
<CommandPalette.Group key={group.id} items={group.items}>
<CommandPalette.GroupLabel>
{group.label}
</CommandPalette.GroupLabel>
<CommandPalette.Items>
{(item: CommandItem) => (
<CommandPalette.Item
key={item.id}
value={item}
onClick={() => setOpen(false)}
>
<span className="flex items-center gap-3">
{item.icon && (
<span className="text-lark-subtle">
{item.icon}
</span>
)}
<span>{item.title}</span>
</span>
</CommandPalette.Item>
)}
</CommandPalette.Items>
</CommandPalette.Group>
)}
</CommandPalette.Results>
<CommandPalette.Empty>未找到结果</CommandPalette.Empty>
</>
)}
</CommandPalette.List>
</CommandPalette.Root>
</div>
);
}禁用浏览器自动填充
传入 autoComplete、autoCorrect、spellCheck 以及 data-* 等标准 HTML 输入属性,可抑制浏览器和密码管理器的自动填充浮层。
import { useState } from "react";
import { CommandPalette, Button } from "@lark-ui/core";
/** 演示在命令面板(CommandPalette)输入框上关闭浏览器自动补全和拼写检查。 */
export function CommandPaletteNoAutocompleteDemo() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const filteredGroups = filterGroupsWithItems(sampleGroups, search);
return (
<div className="flex flex-col items-start gap-4">
<Button onClick={() => setOpen(true)}>打开面板(禁用自动补全)</Button>
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={filteredGroups}
value={search}
onValueChange={setSearch}
itemToStringValue={(group) => group.label}
onSelect={(item) => {
console.log("Selected:", item.title);
setOpen(false);
setSearch("");
}}
getSelectableItems={getSelectableItems}
>
<CommandPalette.Input
placeholder="搜索命令…"
autoComplete="off"
autoCorrect="off"
autoCapitalize="none"
spellCheck={false}
data-1p-ignore="true"
data-lpignore="true"
/>
<CommandPalette.List>
<CommandPalette.Results>
{(group: CommandGroup) => (
<CommandPalette.Group key={group.id} items={group.items}>
<CommandPalette.GroupLabel>
{group.label}
</CommandPalette.GroupLabel>
<CommandPalette.Items>
{(item: CommandItem) => (
<CommandPalette.Item
key={item.id}
value={item}
onClick={() => {
setOpen(false);
setSearch("");
}}
>
<span className="flex items-center gap-3">
{item.icon && (
<span className="text-lark-subtle">{item.icon}</span>
)}
<span>{item.title}</span>
</span>
</CommandPalette.Item>
)}
</CommandPalette.Items>
</CommandPalette.Group>
)}
</CommandPalette.Results>
<CommandPalette.Empty>未找到命令</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</div>
);
}带面包屑的 ResultItem
使用 ResultItem 展示包含面包屑、图标以及可选文本高亮的富内容选项。
import { useState } from "react";
import { CommandPalette, Button } from "@lark-ui/core";
export function CommandPaletteResultItemDemo() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
return (
<div>
<Button onClick={() => setOpen(true)}>打开(带 ResultItem)</Button>
<CommandPalette.Root
open={open}
onOpenChange={setOpen}
items={searchResults}
value={search}
onValueChange={setSearch}
itemToStringValue={(item) => item.title}
getSelectableItems={(items) => items}
>
<CommandPalette.Input placeholder="搜索文档…" />
<CommandPalette.List>
<CommandPalette.Results>
{(item: SearchResult) => (
<CommandPalette.ResultItem
key={item.id}
value={item}
title={item.title}
breadcrumbs={item.breadcrumbs}
icon={item.icon}
onClick={() => {
console.log("Navigate to:", item.title);
setOpen(false);
}}
/>
)}
</CommandPalette.Results>
<CommandPalette.Empty>未找到页面</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span className="flex items-center gap-2">
<kbd className="rounded border border-lark-hairline bg-lark-base px-1.5 py-0.5 text-[10px]">
↑↓
</kbd>
<span>导航</span>
</span>
<span className="flex items-center gap-2">
<kbd className="rounded border border-lark-hairline bg-lark-base px-1.5 py-0.5 text-[10px]">
⌘↵
</kbd>
<span>在新标签页打开</span>
</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</div>
);
}组件构成
CommandPalette.Root
组合 Dialog 与 Panel 的主容器。管理打开状态和自动补全(Autocomplete)功能。
CommandPalette.Dialog
模态弹层容器。与 Panel 搭配使用可切换内容(例如逐级下钻导航)。
CommandPalette.Panel
不含弹层的自动补全面板。放在 Dialog 内使用,可切换内容而无需重新挂载。
CommandPalette.Input
带自动聚焦和键盘处理的搜索输入框。
CommandPalette.List
结果的可滚动容器。
CommandPalette.Results
用于遍历选项/分组的 render 属性迭代器。
CommandPalette.Group
按类别分组的容器。
CommandPalette.GroupLabel
分组内的区块标题文字。
CommandPalette.Items
用于遍历分组内选项的 render 属性迭代器。
CommandPalette.Item
基础可选项。
CommandPalette.ResultItem
包含面包屑、图标和文本高亮的富内容选项。
CommandPalette.HighlightedText
根据匹配位置渲染带高亮片段的文本。
CommandPalette.Empty
没有匹配结果时的空状态。
CommandPalette.Loading
加载动画状态。
CommandPalette.Footer
用于放置快捷键提示或其他内容的底部区域。
API 参考
CommandPalette.Root 属性
interface CommandPaletteRootProps<TGroup, TItem> {
// Dialog state
open: boolean;
onOpenChange: (open: boolean) => void;
onBackdropClick?: (e: React.MouseEvent) => void;
// Autocomplete
items: TGroup[];
value?: string;
onValueChange?: (value: string) => void;
itemToStringValue?: (item: TGroup) => string;
filter?: (item: TGroup, query: string) => boolean;
onItemHighlighted?: (item: TGroup | undefined, details: {...}) => void;
// Selection
onSelect?: (item: TItem, options: { newTab: boolean }) => void;
getSelectableItems?: (items: TGroup[]) => TItem[];
children: React.ReactNode;
}CommandPalette.ResultItem 属性
interface CommandPaletteResultItemProps<T> {
value: T;
title: string;
breadcrumbs?: string[];
titleHighlights?: [number, number][];
breadcrumbHighlights?: [number, number][][];
description?: string;
icon?: React.ReactNode;
onClick: (e: React.MouseEvent) => void;
showArrow?: boolean; // default: true
external?: boolean; // shows external link icon
nonInteractive?: boolean;
}