Primitives
Command
A search field above a list that the user filters and moves through with the keyboard.
Primitives
A search field above a list that the user filters and moves through with the keyboard.
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
toast,
useModKey,
} from 'ferry-ui'
import { FolderKanban, Plus, Settings, UserPlus } from 'lucide-react'
export default function CommandHero() {
const mod = useModKey()
return (
<Command label="Commands" className="max-w-md border border-border-strong">
<CommandInput placeholder="Type a command or search…" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Projects">
<CommandItem onSelect={() => toast('Open the project Billing portal')}>
<FolderKanban /> Billing portal
</CommandItem>
<CommandItem onSelect={() => toast('Open the project Customer dashboard')}>
<FolderKanban /> Customer dashboard
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem onSelect={() => toast('New project')}>
<Plus /> New project
<CommandShortcut>{mod} N</CommandShortcut>
</CommandItem>
<CommandItem onSelect={() => toast('Invite a member')}>
<UserPlus /> Invite a member
<CommandShortcut>{mod} I</CommandShortcut>
</CommandItem>
<CommandItem onSelect={() => toast('Open the settings')}>
<Settings /> Open settings
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}Command for a long list that the user filters with the keyboard.Command in a Popover to pick one value in a long list.CommandDialog.Import the parts. Put them together in this order.
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from 'ferry-ui'
<Command>
<CommandInput />
<CommandList>
<CommandEmpty />
<CommandGroup>
<CommandItem>
<CommandShortcut />
</CommandItem>
</CommandGroup>
<CommandSeparator />
</CommandList>
</Command>
<CommandDialog>
<CommandInput />
<CommandList />
</CommandDialog>| Part | Role |
|---|---|
Command | The root. It filters the items and holds the selection. |
CommandInput | The search field. |
CommandList | Holds the groups and the items. It scrolls. |
CommandEmpty | The message that shows when no item matches. |
CommandGroup | A section of items. heading sets its title. |
CommandItem | One row that the user can select. |
CommandSeparator | A line between two groups. |
CommandShortcut | A key hint at the right of an item. It binds no key. |
CommandDialog | A Command in a dialog. |
CommandItem runs onSelect on a click and on Enter. The search reads the text of each item. Use value to give a different text to the search. Use keywords to add more words.
Set disabled on an item that the user cannot select.
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, toast } from 'ferry-ui'
import { CreditCard, KeyRound, LayoutDashboard, Users } from 'lucide-react'
export default function CommandItems() {
return (
<Command label="Pages" className="max-w-sm border border-border-strong">
<CommandInput placeholder="Go to a page…" />
<CommandList>
<CommandEmpty>No page found.</CommandEmpty>
<CommandGroup heading="Go to">
<CommandItem onSelect={() => toast('Open the dashboard')}>
<LayoutDashboard /> Dashboard
</CommandItem>
{/* `keywords`: the search for "invoices" also finds this item. */}
<CommandItem keywords={['invoices', 'payment', 'plan']} onSelect={() => toast('Open the billing page')}>
<CreditCard /> Billing
</CommandItem>
{/* `value`: the search reads this text, not the content with the count. */}
<CommandItem value="Members" onSelect={() => toast('Open the members page')}>
<Users /> Members
<span className="ml-auto text-xs text-foreground-lighter">12</span>
</CommandItem>
<CommandItem disabled>
<KeyRound /> API keys
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}Put Command in PopoverContent to make a picker with a search. Control the open state of the popover. Close the popover in onSelect.
import * as React from 'react'
import {
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ferry-ui'
import { Check, ChevronsUpDown } from 'lucide-react'
const MEMBERS = [
{ id: 'maya', name: 'Maya Chen' },
{ id: 'sam', name: 'Sam Lee' },
{ id: 'ada', name: 'Ada Park' },
{ id: 'liam', name: 'Liam Chen' },
{ id: 'sofia', name: 'Sofia Rossi' },
]
export default function CommandCombobox() {
const [open, setOpen] = React.useState(false)
const [owner, setOwner] = React.useState<string>()
const current = MEMBERS.find((member) => member.id === owner)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button role="combobox" aria-expanded={open} iconRight={<ChevronsUpDown />} className="w-56 justify-between">
{current?.name ?? 'Select an owner'}
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-56 p-0" aria-label="Owner">
<Command>
<CommandInput placeholder="Find a member…" />
<CommandList>
<CommandEmpty>No member found.</CommandEmpty>
<CommandGroup heading="Members">
{MEMBERS.map((member) => (
<CommandItem
key={member.id}
value={member.name}
onSelect={() => {
setOwner(member.id)
setOpen(false)
}}
>
{member.name}
{member.id === owner && <Check className="ml-auto" />}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}If your code filters the list, pass shouldFilter={false}. This is necessary for a search on a server. Control CommandInput with value and onValueChange. Render only the items that match.
import * as React from 'react'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, toast } from 'ferry-ui'
import { FileText } from 'lucide-react'
const INVOICES = [
{ id: 'INV-2041', customer: 'Acme', amount: '$4,200.00' },
{ id: 'INV-2042', customer: 'Maya Chen', amount: '$860.00' },
{ id: 'INV-2043', customer: 'Sam Lee', amount: '$12,940.50' },
{ id: 'INV-2044', customer: 'Ada Park', amount: '$1,315.00' },
]
export default function CommandOwnFilter() {
const [query, setQuery] = React.useState('')
const text = query.trim().toLowerCase()
// Your code filters the list. With a search on the server, the results come from the request.
const results = INVOICES.filter((invoice) => `${invoice.id} ${invoice.customer}`.toLowerCase().includes(text))
return (
<Command shouldFilter={false} label="Invoices" className="max-w-md border border-border-strong">
<CommandInput value={query} onValueChange={setQuery} placeholder="Search by number or customer…" />
<CommandList>
<CommandEmpty>No invoice matches “{query}”.</CommandEmpty>
{results.length > 0 && (
<CommandGroup heading={`Invoices · ${results.length}`}>
{results.map((invoice) => (
<CommandItem key={invoice.id} value={invoice.id} onSelect={() => toast(`Open the invoice ${invoice.id}`)}>
<FileText />
<span className="font-mono text-xs text-foreground-lighter">{invoice.id}</span>
<span className="truncate">{invoice.customer}</span>
<span className="tabular ml-auto text-xs text-foreground-lighter">{invoice.amount}</span>
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
)
}CommandDialog shows a Command in a Dialog. Pass open and onOpenChange. The commandProps prop takes the props of the Command root.
CommandDialog binds no key. To open it with a shortcut, use useCommandShortcut.
import * as React from 'react'
import { Button, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, toast } from 'ferry-ui'
import { Users } from 'lucide-react'
const MEMBERS = [
{ name: 'Maya Chen', email: 'maya@example.com' },
{ name: 'Sam Lee', email: 'sam@example.com' },
{ name: 'Ada Park', email: 'ada@example.com' },
{ name: 'Liam Chen', email: 'liam@example.com' },
]
export default function CommandInDialog() {
const [open, setOpen] = React.useState(false)
return (
<>
<Button icon={<Users />} onClick={() => setOpen(true)}>
Find a member
</Button>
<CommandDialog
open={open}
onOpenChange={setOpen}
title="Find a member"
description="Search the members of the workspace by name or by email."
>
<CommandInput placeholder="Search by name or email…" />
<CommandList>
<CommandEmpty>No member found.</CommandEmpty>
<CommandGroup heading="Members">
{MEMBERS.map((member) => (
<CommandItem
key={member.email}
value={`${member.name} ${member.email}`}
onSelect={() => {
toast(`Open the profile of ${member.name}`)
setOpen(false)
}}
>
{member.name}
<span className="ml-auto truncate text-xs text-foreground-lighter">{member.email}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
</>
)
}label prop of Command gives an accessible name to the search field. It does not show.CommandDialog has a hidden title and a hidden description. Set them with title and description.role="combobox" and aria-expanded to the trigger. Give an aria-label to PopoverContent.Each part also accepts the props of its cmdk primitive and the attributes of its element.
| Prop | Type | Default |
|---|---|---|
children | ReactNode | - |
| See the element or the Radix UI primitive this part renders. | ||
defaultValue | string | (readonly string[] & string) | - |
Optional default item value when it is initially rendered. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
label | string | - |
Accessible label for this command menu. Not shown visibly. | ||
shouldFilter | boolean | - |
Optionally set to | ||
filter | CommandFilter | - |
Custom filter function for whether each command menu item should matches the given search query. It should return a number between 0 and 1, with 1 being the best match and 0 being hidden entirely. By default, uses the | ||
value | string | - |
Optional controlled state of the selected command menu item. | ||
onValueChange | ((value: string) => void) | - |
Event handler called when the selected item of the menu changes. | ||
loop | boolean | - |
Optionally set to | ||
disablePointerSelection | boolean | - |
Optionally set to | ||
vimBindings | boolean | - |
Set to | ||
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
value | string | - |
Optional controlled state for the value of the search input. | ||
onValueChange | ((search: string) => void) | - |
Event handler called when the search value changes. | ||
| Prop | Type | Default |
|---|---|---|
children | ReactNode | - |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
label | string | - |
Accessible label for this List of suggestions. Not shown visibly. | ||
| Prop | Type | Default |
|---|---|---|
children | ReactNode | - |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
| Prop | Type | Default |
|---|---|---|
children | ReactNode | - |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
heading | ReactNode | - |
Optional heading to render for this group. | ||
value | string | - |
If no heading is provided, you must provide a value that is unique for this group. | ||
forceMount | boolean | - |
Whether this group is forcibly rendered regardless of filtering. | ||
| Prop | Type | Default |
|---|---|---|
children | ReactNode | - |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
disabled | boolean | - |
Whether this item is currently disabled. | ||
onSelect | ((value: string) => void) | - |
Event handler for when this item is selected, either via click or keyboard selection. | ||
value | string | - |
A unique value for this item. If no value is provided, it will be inferred from | ||
keywords | string[] | - |
Optional keywords to match against when filtering. | ||
forceMount | boolean | - |
Whether this item is forcibly rendered regardless of filtering. | ||
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
alwaysRender | boolean | - |
Whether this separator should always be rendered. Useful if you disable automatic filtering. | ||
CommandShortcut has no props of its own. It accepts the attributes of the element it renders.
CommandDialog also accepts the props of the Dialog root.
| Prop | Type | Default |
|---|---|---|
title | string | Command Palette |
Accessible dialog title, visually hidden (default "Command Palette"). | ||
description | string | Search for a command to run... |
Accessible dialog description, visually hidden (default "Search for a command to run..."). | ||
className | string | - |
Classes merged onto the dialog panel (e.g. | ||
showCloseButton | boolean | true |
Show the top-right close button (default true). | ||
closeLabel | string | - |
Accessible name of the close button (default "Close"); pass a translation in localized apps. | ||
commandProps | Omit<ComponentProps<typeof CommandPrimitive>, "children"> | - |
Props for the inner Command root, e.g. | ||
children | ReactNode | - |
| See the element or the Radix UI primitive this part renders. | ||
open | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
defaultOpen | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
onOpenChange | ((open: boolean) => void) | - |
| See the element or the Radix UI primitive this part renders. | ||
modal | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||