HextaUI

Command

A searchable list of actions, inline or as a ⌘K palette, with pages, shortcuts and highlighted matches.

import {
  IconCalculator,
  IconCalendar,
  IconCreditCard,
  IconMoodSmile,
  IconSettings,
  IconUser,
} from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command"

export function CommandDemo() {
  return (
    <Command highlight className="w-full max-w-sm">
      <CommandInput placeholder="Type a command or search…" />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Suggestions">
          <CommandItem>
            <IconCalendar />
            Calendar
          </CommandItem>
          <CommandItem>
            <IconMoodSmile />
            Search Emoji
          </CommandItem>
          <CommandItem disabled>
            <IconCalculator />
            Calculator
          </CommandItem>
        </CommandGroup>
        <CommandSeparator />
        <CommandGroup heading="Settings">
          <CommandItem shortcut="mod+p">
            <IconUser />
            Profile
          </CommandItem>
          <CommandItem shortcut="mod+b">
            <IconCreditCard />
            Billing
          </CommandItem>
          <CommandItem shortcut="mod+,">
            <IconSettings />
            Settings
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/command.json

Adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command"
<Command>
  <CommandInput placeholder="Type a command or search…" />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem onSelect={() => openCalendar()}>Calendar</CommandItem>
      <CommandItem shortcut="mod+p">Profile</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>

Hotkeys use mod for ⌘ on Apple devices and Ctrl everywhere else. Labels are formatted per platform for you.

Command
├── CommandInput
├── CommandList
│   ├── CommandEmpty
│   ├── CommandLoading
│   ├── CommandGroup
│   │   └── CommandItem
│   │       └── CommandShortcut
│   ├── CommandSeparator
│   └── CommandPage
│       └── CommandGroup
└── CommandFooter

CommandDialog
└── Command

Basic

Typing filters and ranks items as you go. Groups with no matches disappear, and the list height animates to fit what is left.

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command"

export function CommandBasic() {
  return (
    <Command className="w-full max-w-sm">
      <CommandInput placeholder="Type a command or search…" />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Suggestions">
          <CommandItem>Calendar</CommandItem>
          <CommandItem>Search Emoji</CommandItem>
          <CommandItem>Calculator</CommandItem>
        </CommandGroup>
        <CommandSeparator />
        <CommandGroup heading="Settings">
          <CommandItem>Profile</CommandItem>
          <CommandItem>Billing</CommandItem>
          <CommandItem>Settings</CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}

Dialog

Put a <Command /> inside <CommandDialog /> and toggle it with useCommandHotkey. Press ⌘K or Ctrl K. Item shortcuts run while it is open, matches are highlighted, and preserveSearch keeps the query and selection for the next time it opens.

Nothing run yet. Theme: System.

"use client"

import * as React from "react"
import {
  IconCalculator,
  IconCalendar,
  IconCheck,
  IconCreditCard,
  IconExternalLink,
  IconMoodSmile,
  IconPalette,
  IconPoint,
  IconSettings,
  IconUser,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandFooter,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPage,
  CommandSeparator,
  CommandShortcut,
  useCommandHotkey,
} from "@/components/ui/command"

export function CommandDialogDemo() {
  const [open, setOpen] = React.useState(false)
  const [last, setLast] = React.useState<string>()
  const [theme, setTheme] = React.useState("System")

  useCommandHotkey("mod+k", () => setOpen((value) => !value))

  const run = (action: string) => {
    setLast(action)
    setOpen(false)
  }

  return (
    <div className="flex flex-col items-center gap-3">
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open palette
        <CommandShortcut hotkey="mod+k" />
      </Button>
      <p className="text-sm text-muted-foreground">
        {last ? `Ran “${last}”.` : "Nothing run yet."} Theme: {theme}.
      </p>
      <CommandDialog open={open} onOpenChange={setOpen} preserveSearch>
        <Command highlight>
          <CommandInput placeholder="Type a command or search…" />
          <CommandList>
            <CommandEmpty>
              {(search) => `No results for “${search}”.`}
            </CommandEmpty>
            <CommandGroup heading="Suggestions">
              <CommandItem
                shortcut="mod+shift+c"
                onSelect={() => run("Calendar")}
              >
                <IconCalendar />
                Calendar
              </CommandItem>
              <CommandItem onSelect={() => run("Search Emoji")}>
                <IconMoodSmile />
                Search Emoji
              </CommandItem>
              <CommandItem disabled>
                <IconCalculator />
                Calculator
              </CommandItem>
              <CommandItem page="theme" pageTitle="Theme">
                <IconPalette />
                Change theme…
              </CommandItem>
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup heading="Settings">
              <CommandItem shortcut="mod+p" onSelect={() => run("Profile")}>
                <IconUser />
                Profile
              </CommandItem>
              <CommandItem shortcut="mod+b" onSelect={() => run("Billing")}>
                <IconCreditCard />
                Billing
              </CommandItem>
              <CommandItem
                shortcut="mod+,"
                keywords={["preferences", "options"]}
                onSelect={() => run("Settings")}
              >
                <IconSettings />
                Settings
              </CommandItem>
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup heading="Links">
              <CommandItem href="/docs" onSelect={() => setOpen(false)}>
                <IconExternalLink />
                All components
              </CommandItem>
            </CommandGroup>
            <CommandPage id="theme">
              <CommandGroup heading="Theme">
                {["Light", "Dark", "System"].map((option) => (
                  <CommandItem
                    key={option}
                    onSelect={() => {
                      setTheme(option)
                      run(`Theme: ${option}`)
                    }}
                  >
                    {option === theme ? <IconCheck /> : <IconPoint />}
                    {option}
                  </CommandItem>
                ))}
              </CommandGroup>
            </CommandPage>
          </CommandList>
          <CommandFooter />
        </Command>
      </CommandDialog>
    </div>
  )
}

Pages

An item with page opens the matching <CommandPage />. The page title appears as a chip in the input, the list slides in from the side, and Backspace on an empty search or Escape goes back.

Nothing selected yet.

"use client"

import * as React from "react"
import {
  IconBrandGithub,
  IconFolder,
  IconFolderPlus,
  IconUsers,
} from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPage,
} from "@/components/ui/command"

const projects = ["hextaui", "marketing-site", "design-tokens"]
const members = ["Ada Lovelace", "Grace Hopper", "Alan Turing"]

export function CommandPages() {
  const [last, setLast] = React.useState<string>()

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Command>
        <CommandInput placeholder="Search…" />
        <CommandList>
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandGroup heading="Workspace">
            <CommandItem page="projects" pageTitle="Projects">
              <IconFolder />
              Projects
            </CommandItem>
            <CommandItem page="members" pageTitle="Members">
              <IconUsers />
              Members
            </CommandItem>
            <CommandItem onSelect={() => setLast("New project")}>
              <IconFolderPlus />
              New project
            </CommandItem>
          </CommandGroup>
          <CommandPage id="projects">
            <CommandGroup heading="Projects">
              {projects.map((project) => (
                <CommandItem key={project} onSelect={setLast}>
                  <IconBrandGithub />
                  {project}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandPage>
          <CommandPage id="members">
            <CommandGroup heading="Members">
              {members.map((member) => (
                <CommandItem key={member} onSelect={setLast}>
                  <IconUsers />
                  {member}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandPage>
        </CommandList>
      </Command>
      <p className="text-sm text-muted-foreground">
        {last ? `Selected “${last}”.` : "Nothing selected yet."}
      </p>
    </div>
  )
}

Scrollable

Long lists scroll inside a capped height. The selected item is always kept in view while you move with the keyboard.

import { IconFileText } from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"

const documents = Array.from({ length: 60 }, (_, index) => ({
  id: index + 1,
  title: `Document ${String(index + 1).padStart(2, "0")}`,
}))

export function CommandScrollable() {
  return (
    <Command className="w-full max-w-sm">
      <CommandInput placeholder="Search 60 documents…" />
      <CommandList>
        <CommandEmpty>No documents match.</CommandEmpty>
        <CommandGroup heading="Documents">
          {documents.map((doc) => (
            <CommandItem key={doc.id}>
              <IconFileText />
              {doc.title}
            </CommandItem>
          ))}
        </CommandGroup>
      </CommandList>
    </Command>
  )
}

Async results

Set shouldFilter={false} and render the results you fetch. <CommandLoading /> waits 150 ms before it appears and then stays at least 300 ms, so fast responses never flash a spinner. Try both latencies.

"use client"

import * as React from "react"
import { IconUser } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandLoading,
  useCommandLoading,
} from "@/components/ui/command"

const people = [
  "Ada Lovelace",
  "Alan Turing",
  "Grace Hopper",
  "Katherine Johnson",
  "Linus Torvalds",
  "Margaret Hamilton",
  "Tim Berners-Lee",
]

export function CommandAsync() {
  const [query, setQuery] = React.useState("")
  const [results, setResults] = React.useState(people)
  const [loading, setLoading] = React.useState(false)
  const [latency, setLatency] = React.useState(700)
  const pending = useCommandLoading(loading)
  const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined)

  React.useEffect(() => () => clearTimeout(timerRef.current), [])

  const search = (nextQuery: string, nextLatency: number) => {
    clearTimeout(timerRef.current)
    setLoading(true)
    timerRef.current = setTimeout(() => {
      setResults(
        people.filter((person) =>
          person.toLowerCase().includes(nextQuery.toLowerCase())
        )
      )
      setLoading(false)
    }, nextLatency)
  }

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <div className="flex gap-2">
        {[80, 700].map((ms) => (
          <Button
            key={ms}
            size="sm"
            variant={latency === ms ? "secondary" : "outline"}
            onClick={() => {
              setLatency(ms)
              search(query, ms)
            }}
          >
            {ms} ms
          </Button>
        ))}
      </div>
      <Command shouldFilter={false} highlight>
        <CommandInput
          placeholder="Search people…"
          value={query}
          onValueChange={(next) => {
            setQuery(next)
            search(next, latency)
          }}
        />
        <CommandList>
          <CommandLoading loading={loading}>Searching…</CommandLoading>
          <CommandEmpty>
            {(value) => `No people match “${value}”.`}
          </CommandEmpty>
          {pending ? null : (
            <CommandGroup heading="People">
              {results.map((person) => (
                <CommandItem key={person}>
                  <IconUser />
                  {person}
                </CommandItem>
              ))}
            </CommandGroup>
          )}
        </CommandList>
      </Command>
    </div>
  )
}

Long content

Headings wrap, long names truncate or wrap as you choose, and shortcuts never get pushed out.

import { IconFileText } from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/components/ui/command"

export function CommandLongContent() {
  return (
    <Command className="w-full max-w-72">
      <CommandInput placeholder="Search…" />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="A group heading that is long enough to wrap onto two lines">
          <CommandItem>
            <IconFileText />
            <span className="min-w-0 truncate">
              quarterly-planning-final-final-v2-reviewed-by-legal.pdf
            </span>
            <CommandShortcut>⌘⇧O</CommandShortcut>
          </CommandItem>
          <CommandItem>
            <IconFileText />
            <span className="min-w-0 wrap-anywhere">
              averyveryverylongunbrokenfilenamethatshouldwrapinsteadofescaping.txt
            </span>
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}

Right to left

Icons, shortcuts, the page chip and the page slide all follow the reading direction.

import { IconCalendar, IconSettings } from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/components/ui/command"

export function CommandRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <Command dir="rtl">
        <CommandInput placeholder="ابحث عن أمر…" />
        <CommandList>
          <CommandEmpty>لا توجد نتائج.</CommandEmpty>
          <CommandGroup heading="اقتراحات">
            <CommandItem>
              <IconCalendar />
              التقويم
              <CommandShortcut>⌘T</CommandShortcut>
            </CommandItem>
            <CommandItem>
              <IconSettings />
              الإعدادات
              <CommandShortcut>⌘S</CommandShortcut>
            </CommandItem>
          </CommandGroup>
        </CommandList>
      </Command>
    </div>
  )
}
KeyAction
↓Selects the next item.
↑Selects the previous item.
Alt↓Jumps to the first item of the next group.
Alt↑Jumps to the first item of the previous group.
HomeSelects the first item.
EndSelects the last item.
CtrlNSelects the next item. Ctrl J works too. Turn off with vimBindings.
CtrlPSelects the previous item. Ctrl K works too. Turn off with vimBindings.
EnterRuns the selected item. On a link item, ⌘ Enter or Ctrl Enter opens it in a new tab.
EscClears the search first, then goes back one page, then closes the dialog.
BackspaceGoes back one page when the search is empty.
⌘PAny item shortcut runs its item while focus is inside the command menu.
  • The input is a combobox that points at the selected item, so screen readers announce each item as you move.
  • A polite live region announces the number of results shortly after you stop typing, and announces the page title when you open or leave a page. Change the wording with formatResults and rootTitle.
  • <CommandDialog /> has a hidden title and description, traps focus while open and returns it to the trigger when it closes.
  • Item shortcuts are exposed with aria-keyshortcuts.
  • With reduced motion on, items run without the confirm blink and pages fade instead of sliding.

Built on cmdk, with <CommandDialog /> on the Base UI dialog. Parts accept the props of the cmdk part they wrap.

PropTypeDefault
labelAccessible name of the menu.
string"Command menu"
highlightHighlights the matched letters in each item and dims the rest.
booleanfalse
shouldFilterSet to false to filter and sort the items yourself, for example when results come from a server.
booleantrue
filterReturns a score from 0 (hidden) to 1 (best match).
(value: string, search: string, keywords?: string[]) => number–
valueThe selected item’s value.
string–
defaultValue
string–
onValueChange
(value: string) => void–
loopWrap around at the ends of the list.
booleanfalse
vimBindingsCtrl N, J, P and K navigation.
booleantrue
disablePointerSelection
booleanfalse
formatResultsText announced to screen readers after typing.
(count: number) => string"3 results"
rootTitleAnnounced when you leave the last page and return to the root.
string"All commands"
AttributeDescription
data-slot="command"Target the root in CSS.
data-highlightingPresent while highlight is on and the search is not empty.
--command-radiusOuter radius. Items derive a concentric radius from it.
--command-insetPadding between the list edge and its items.
PropTypeDefault
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
preserveSearchKeep the dialog mounted so the query, page and selection survive closing. The query is selected when it reopens.
booleanfalse
titleVisually hidden dialog title.
string"Command menu"
descriptionVisually hidden dialog description.
string"Search for a command to run."
showCloseButton
booleanfalse
classNameApplied to the dialog popup.
string–
AttributeDescription
data-slot="command-dialog"The dialog popup.
data-slot="command-dialog-overlay"The backdrop.
data-openPresent on the popup while it is open.
PropTypeDefault
valueControlled search text.
string–
onValueChange
(search: string) => void–
placeholder
string–
clearLabelAccessible name of the clear button.
string"Clear search"
backLabelAccessible name of the page chip.
(title: string) => string(title) => `Back from ${title}`
AttributeDescription
data-slot="command-input"The input.
data-slot="command-input-wrapper"The row holding the icon, input and clear button.
data-slot="command-clear"The clear button, shown once you type.
data-slot="command-page-chip"The back chip shown on a page.
PropTypeDefault
labelAccessible name of the list.
string–
AttributeDescription
data-slot="command-list"The list.
data-settledPresent once the list has measured itself. The height transition only runs while it is set.
--cmdk-list-heightHeight of the visible items, used to animate the list.
PropTypeDefault
childrenUse the function form to echo the query.
ReactNode | (search: string) => ReactNode–
AttributeDescription
data-slot="command-empty"Hidden while a CommandLoading is in the list.
PropTypeDefault
loading
booleantrue
delayMilliseconds to wait before the spinner shows.
number150
minDurationMinimum milliseconds the spinner stays once shown.
number300
labelAccessible label. Defaults to string children.
string–
progress
number–
AttributeDescription
data-slot="command-loading"The loading row.
data-pendingPresent during the delay, while the row is announced but not yet visible.
PropTypeDefault
heading
ReactNode–
valueRequired when there is no heading.
string–
forceMountKeep the group visible while filtering.
booleanfalse
AttributeDescription
data-slot="command-group"The group.
[cmdk-group-heading]The heading element.
PropTypeDefault
onSelectRuns on click, Enter or the item’s shortcut, after the confirm blink.
(value: string) => void–
valueUsed for filtering. Defaults to the item’s text, without the shortcut.
string–
keywordsExtra words that match this item.
string[]–
disabled
booleanfalse
shortcutA hotkey like "mod+shift+c". Shown on the item and runs it while focus is in the menu.
string–
pageOpens the CommandPage with this id instead of running.
string–
pageTitleTitle shown in the page chip. Defaults to the value.
string–
hrefRenders the item as a link. Enter follows it, ⌘ or Ctrl Enter opens a new tab.
string–
renderA link element to render instead, like Next.js <Link />.
ReactElement–
confirmBlink the item briefly before running it, so the choice registers.
booleantrue
forceMountKeep the item visible while filtering.
booleanfalse
AttributeDescription
data-slot="command-item"The item.
data-selected="true"Present on the selected item.
data-disabled="true"Present on disabled items.
data-valueThe value used for filtering.
data-confirmingPresent during the confirm blink.
data-pagePresent on items that open a page.
PropTypeDefault
idMatches the page prop of the item that opens it. Its groups and items only render while it is the current page.
string–
PropTypeDefault
hotkeyFormats a hotkey like "mod+k" for the current platform. Children override it.
string–
AttributeDescription
data-slot="command-shortcut"The shortcut label.
PropTypeDefault
alwaysRenderKeep it visible while searching.
booleanfalse
AttributeDescription
data-slot="command-separator"The separator.
PropTypeDefault
childrenDefaults to key hints that update on a page. Hidden on touch screens.
ReactNode–
AttributeDescription
data-slot="command-footer"The footer.
const [open, setOpen] = React.useState(false)

useCommandHotkey("mod+k", () => setOpen((value) => !value))
PropTypeDefault
hotkeyListened for on the whole document. Hotkeys without a modifier are ignored while typing in a field.
string–
callback
(event: KeyboardEvent) => void–
options.enabled
booleantrue

Returns whether a loading indicator should be visible, with the same delay and minimum duration as <CommandLoading />. Use it to hide stale results while a request is in flight.

PropTypeDefault
loading
boolean–
options.delay
number150
options.minDuration
number300
  • useCommandPages() returns { pages, page, push, pop, reset } to drive pages from your own code.
  • useCommandState(selector) reads cmdk state, such as the search or the filtered count.
  • useHotkeyLabel(hotkey) formats a hotkey for the current platform, like ⌘K or Ctrl+K.