DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Command

Command palette / ⌘K menu — filterable, keyboard-driven action launcher with inline and modal modes, grouped items, async states, scope toolbar, rich-content panel, and footer key hints.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  Command,
  useCommandShortcut,
} from "@tessinaui/ui";

Inline command palette

Embed the palette directly in the page. The list is always visible — typing into the input filters items live.

<Command.Root size="md" rounded="md">
  <Command.Input placeholder="Type a command or search…" shortcut="⌘K" />
  <Command.List>
    <Command.Empty />
    <Command.Group heading="Suggestions">
      <Command.Item value="dashboard" leadingIcon={<LayoutDashboard />}>
        <Command.ItemTitle>Dashboard</Command.ItemTitle>
        <Command.Shortcut>⌘D</Command.Shortcut>
      </Command.Item>
      <Command.Item value="settings" leadingIcon={<Settings />}>
        <Command.ItemTitle>Settings</Command.ItemTitle>
      </Command.Item>
    </Command.Group>
  </Command.List>
</Command.Root>

Modal command palette

Wrap the same content in Command.Dialog to make a ⌘K overlay. Pair with useCommandShortcut("k", …) for a global keyboard trigger. The dialog stays a centered card at every size; below md it sits just under the safe area and caps at min(45dvh, 24rem) so the input, the results and the footer all stay above a virtual keyboard (mobilePresentation="fullscreen" opts into the takeover instead).

function App() {
  const [open, setOpen] = useState(false);
  useCommandShortcut("k", () => setOpen((o) => !o));

  return (
    <Command.Dialog open={open} onOpenChange={setOpen}>
      <Command.Input placeholder="Search…" />
      <Command.List>
        <Command.Empty />
        <Command.Group heading="Actions">
          <Command.Item value="new-file" onSelect={() => createFile()}>
            <Command.ItemTitle>New file</Command.ItemTitle>
            <Command.Shortcut>⌘N</Command.Shortcut>
          </Command.Item>
        </Command.Group>
      </Command.List>
      <Command.Footer>
        <Command.FooterHint shortcut="enter">To select</Command.FooterHint>
        <Command.FooterHint shortcut="arrows">To navigate</Command.FooterHint>
        <Command.FooterHint shortcut="escape" appearance="ghost">To close</Command.FooterHint>
      </Command.Footer>
    </Command.Dialog>
  );
}

Items with rich content

Items can hold a title, optional description, inline trailing metadata, a breadcrumb path, and a trailing shortcut. Command.HighlightMatch emphasises the query inside any text.

<Command.Item value="settings-team" keywords={["prefs"]} leadingIcon={<Users />}>
  <Command.ItemTitle>
    <Command.HighlightMatch>Team settings</Command.HighlightMatch>
  </Command.ItemTitle>
  <Command.ItemDescription>Invite or remove members</Command.ItemDescription>
  <Command.ItemMeta>workspace</Command.ItemMeta>
  <Command.Shortcut>⌘T</Command.Shortcut>
</Command.Item>

<Command.Item value="docs-deep-link">
  <Command.Crumbs crumbs={["Components", "Form", "Field"]} />
</Command.Item>

Examples

Default

An inline command palette with grouped suggestions, icons, and shortcuts. With text in the field, Esc clears it (with it empty, a wrapping dialog's Esc dismisses).

Modal dialog

Command.Trigger + useCommandShortcut + a live result count in the footer.

Mobile presentation

mobilePresentation="fullscreen" opts below md into the takeover every surveyed mobile system ships, with a Cancel button composed from Command.Close in the input's trailing slot. It is opt-in rather than the default because a full-height sheet pins its footer to the layout viewport's bottom edge — and on iOS the keyboard covers that edge without shrinking dvh, so footer hints go out of view. Keep the dismiss affordance in the input row when you use it.

Anchored

The palette hanging off a page control via the house Popover — Command.Root is position-agnostic.

Scope toolbar

Command.Toolbar hosts a result-type switcher between the input and the results. It renders outside the listbox, so interactive children are legal.

Match highlighting

Command.HighlightMatch wraps query occurrences in a de-yellowed <mark>.

Async search

External filtering (disableFiltering), the input's loading spinner, skeleton result rows, and Command.Error with a forceMount retry item — type fail to see the error path.

Actionable empty state

The default Command.Empty copy echoes the query. Recovery actions are pinned options (forceMount items) — a button inside the listbox would be an aria-required-children violation.

Recents chips

Command.Chips with per-chip remove (Delete/Backspace on the focused chip) and a legal "Clear all" action — the row is one Tab stop with arrow-key navigation.

Filtering

keywords aliases, value inference from the visible title, and a custom filter.

Pinned AI row

A forceMount item stays visible under every query — the ask-AI pattern.

Nested pages

The cmdk-endorsed pages recipe on plain composition: a page stack in consumer state, Backspace on an empty input pops, the trail renders as chips.

Rich content layout

Cards, app icons and sections compose inside Command.Panel — a scroll column with no listbox role, so its buttons are valid and Tab-reachable. The sidebar is a real tablist (one Tab stop, Up/Down arrows).

Rich items

Items with descriptions, avatars, metadata, and breadcrumb paths.

Sizes

Five sizes from xs to xl, each with proportional padding, type, and icons. On phones the input floors at 16px text (iOS zoom) and rows at 44px.

Rounded

Six corner steps from none to full. The scale follows the §2 panel ladder — full caps at 24px, the panel maximum.

Intent

A semantic accent on the container border — none, error, warning, success, or info.

States

Empty (query echo), loading (spinner and skeleton variants), error, and the whole-component skeleton.

API Reference

Command.Root / Command.Dialog props

Both accept the same visual props. Command.Root renders inline; Command.Dialog renders inside a Base UI Dialog with backdrop and focus trap.

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg" | "xl""md"Input height, padding, and typography scale
rounded"none" | "sm" | "md" | "lg" | "xl" | "full""full" (Dialog) / "lg" (Root)Container radius on the §2 panel ladder (8→24px; full caps at 24px)
width"narrow" | "default" | "wide""default"Max-width
intent"none" | "error" | "warning" | "success" | "info""none"Semantic accent on the container border
dir"ltr" | "rtl"—Stamped only when set; unset palettes inherit the document direction
filter(value, query) => booleansubstringMatch predicate, applied to value and each keyword
disableFilteringbooleanfalseHand result control to the consumer (async/server search)
value / defaultValueValue—Controlled / uncontrolled selected item value (from Base UI Combobox)
onValueChange(value) => void—Fires when the selection changes
inputValue / defaultInputValuestring—Controlled / uncontrolled text input value
onInputValueChange(value) => void—Fires as the user types (and on Escape-clear, with an escape-key reason)
disabledbooleanfalseDisables all interaction

Forwarding: aria-* land on the input (the element with the combobox role); data-*, ids and handlers land on the wrapper (Root) / dialog popup (Dialog). Both modes split identically.

Command.Dialog additional props

PropTypeDefaultDescription
open / defaultOpenboolean—Controlled / uncontrolled open state
onOpenChange(open, event) => void—Fires when the dialog opens or closes
dismissiblebooleantrueWhether Escape / backdrop click closes the dialog
modalbooleantrueFocus trap + scroll lock
labelstring"Command palette"a11y label for the Dialog popup
mobilePresentation"fullscreen" | "dialog""dialog"Below md: keep the centered card, capped to clear the keyboard, or fill the viewport (safe-area aware, list flexes)
initialFocusBase UI initialFocusinputWhere focus lands on open — opt out where a virtual keyboard would occlude content

Command.Input props

PropTypeDefaultDescription
placeholderstring"Type a command or search…"Placeholder text
leadingIconReactNodesearch iconOverride the leading icon. Pass null to hide
loadingbooleanfalseSwap the leading icon for a spinner (150ms anti-flicker fade-in)
shortcutReactNode—Trailing kbd hint
trailingReactNode—Additional trailing content (mic button, Command.Close Cancel…)
showClearbooleantrueShow the × clear button when the input has a value

With text in the field, Esc clears it and stops; with it already empty the event passes through, so a wrapping Dialog can dismiss — the APG two-stage contract.

Command.Toolbar

A padded, horizontally scrolling control row between the input and the results. Renders outside the listbox, so interactive children are legal — compose SegmentedControl, Select size="xs", Chip filters. No props beyond standard div attributes.

Give the control you compose the palette's own rounded value. The radius cascade crosses composition boundaries: a rounded="full" pill switcher inside a rounded="md" panel reads as a foreign part.

Command.Item props

PropTypeDefaultDescription
valuestringinferredFilter/selection identity. When omitted, the visible title text is used
keywordsstring[]—Extra terms the filter also matches (aliases)
forceMountbooleanfalseAlways visible regardless of the query — pinned rows
leadingIcon / leadingAvatarReactNode—Leading slot (icon xor avatar)
trailingIconReactNode—Trailing slot — icon or status indicator
hideLeadingbooleanfalseHide the leading slot entirely
onSelect(value) => void—Fires on click or Enter
disabledbooleanfalseDisables the item

Command.ItemMeta

Muted inline metadata at the trailing edge of a row — a suffix like "— Build", a domain, an author, a date. A sibling of the title column (like Command.Shortcut), not stacked under it. It joins the option's accessible name, so keep it short; never interactive.

Command.HighlightMatch props

PropTypeDefaultDescription
childrenstring—The text to render with query matches emphasised
highlightClassNamestringsemibold foregroundClasses for the matched segments

Command.Group props

PropTypeDefaultDescription
headingReactNode—Group heading rendered above the items
headingTrailingReactNode—Non-interactive slot at the end of the heading row — a count, a badge, a hint
forceMountbooleanfalseKeep the group visible when every child filters out

Put actions in an item, not in the heading row. Command.List is the combobox listbox, and ARIA gives role="listbox" a closed set of permitted children: option and group. A control in the heading row is therefore an aria-required-children failure at critical severity, and no wrapper rescues it — role="presentation" is ignored on a focusable element, and aria-hidden would only trade the rule for aria-hidden-focus.

Render a group action as a Command.Item. That is a real option, so it is an allowed child and it is reachable with the arrow keys — a button in the heading row is Tab-only, so reaching it means leaving the combobox. Passing a focusable node to headingTrailing warns in development.

<Command.Group heading="Recent searches" headingTrailing={<span>3</span>}>
  <Command.Item value="darlene">…</Command.Item>
  <Command.Item value="clear-recent" leadingIcon={<Eraser />} onSelect={clear}>
    <Command.ItemTitle>Clear recent searches</Command.ItemTitle>
  </Command.Item>
</Command.Group>

Command.Crumbs props

PropTypeDefaultDescription
crumbsReactNode[]—Path segments. Last segment styled as the destination
separatorReactNodechevron rightCustom separator (flips under RTL)

Command.Empty props

PropTypeDefaultDescription
iconReactNode—Optional decorative icon above the text
childrenReactNodequery echoDefault copy is «No results for "query"» (or "No results found" pre-query)

Non-interactive (it lives in the listbox) — put recovery actions in a forceMount Command.Item, Command.Chips, or Command.Footer.

Command.Loading props

PropTypeDefaultDescription
labelReactNode"Loading…"The announcement (portalled to the root's status region) and default visible message. null announces nothing
variant"spinner" | "skeleton""spinner"Centered spinner row, or placeholder result rows
rowsnumber3Skeleton row count
childrenReactNode—Replaces the visible row only — label still announces; keep it non-interactive

Command.Error props

PropTypeDefaultDescription
iconReactNode—Optional icon above the message
labelReactNode"Something went wrong"The announcement (same status-region portal as Loading); null silences
childrenReactNodelabelVisible copy. Non-interactive — pair with a forceMount retry item

Command.Chips / Command.ChipItem props

PropTypeDefaultDescription
label (Chips)ReactNode—Optional label rendered before the pills
action (Chips)ReactNode—Trailing action — e.g. a "Clear all" ghost Button. Legal: the row is outside the listbox
value (ChipItem)string—Filter value
keywords / forceMount (ChipItem)——Same filter model as Command.Item
leadingIcon / leadingAvatarReactNode—Leading visual
onRemove() => void—Shows the × affordance; Delete/Backspace on the focused chip is the keyboard path

The row is one Tab stop; Left/Right arrows move between chips.

Command.Body / Command.Sidebar props

PropTypeDefaultDescription
minHeight (Body)string | number"320px"Min-height of the row layout
value / defaultValue (Sidebar)string—Controlled / uncontrolled active view
onValueChange (Sidebar)(value) => void—Fires when the active item changes
value (SidebarItem)string—Item identifier
leadingIcon / trailingIcon (SidebarItem)ReactNode—Icons

The sidebar is a role="tablist" with the full keyboard contract: one Tab stop, Up/Down arrows move, Home/End jump, Enter/Space activates.

Command.Panel props

PropTypeDefaultDescription
maxHeightstring | number"400px"Maximum height of the scrollable panel

The rich-content column: scrolls and pads like Command.List, carries no listbox role, so cards, chips and app icons are legal, Tab-reachable children. Use Command.List for options, Command.Panel for content.

Command.Section props

PropTypeDefaultDescription
headingReactNode—Heading rendered above the content
headingTrailingReactNode—End-of-heading slot. Interactive is fine inside a Panel; inside a List the listbox rule applies (dev-warned)
forceMountbooleanfalseKeep the section when every filterable child is hidden

Auto-hides when filtering removes every Command.Item, Command.ChipItem, or Command.Card descendant.

Command.Grid / Command.Card props

PropTypeDefaultDescription
columns (Grid)1 | 2 | 3 | 4 | 5 | 63Number of columns. On touch it is a maximum: the grid drops a column rather than squeeze a card under the 44px target
gap (Grid)string"gap-2"Tailwind gap class
value (Card)string—Filter value
keywords / forceMount (Card)——Same filter model as Command.Item
title / descriptionReactNode—Text under the media frame
image (Card)string—Image src (rendered as <img>)
media (Card)ReactNode—Custom content inside the aspect frame (overrides image)
aspect (Card)"square" | "video" | "3/4" | "4/3" | "1/1""3/4"Aspect ratio of the media frame
onSelect (Card)(value) => void—Called when the card is activated

Command.IconRow / Command.AppIcon props

PropTypeDefaultDescription
gap (IconRow)string"gap-3"Tailwind gap class
src (AppIcon)string—Image src for the logo
label (AppIcon)string—Accessible label / tooltip
children (AppIcon)ReactNode—Custom content (overrides src)

useCommandShortcut(key, onTrigger, options?)

Hook for binding a global keyboard trigger.

ArgumentTypeDescription
keystringCase-insensitive key name (e.g. "k")
onTrigger() => voidCallback fired when the chord is pressed
options.modifier"auto" | "ctrl" | "meta" | "alt" | "shift" | "none""auto" (default) detects platform: ⌘ on macOS, Ctrl elsewhere
options.enabledbooleanSet false to suspend listening

Unmodified and shift-only bindings are suppressed while typing in an input, textarea, select, or contenteditable; held-key auto-repeat and IME composition are ignored for every modifier.

Rich content layouts

For Mobbin / Linear / Raycast-style palettes, compose on top of the core API — with interactive rich content in Command.Panel, never Command.List (the listbox may own only options and groups):

  • Command.Chips + Command.ChipItem — horizontal pill row (recents, filters) with a legal action slot
  • Command.Toolbar — scope tabs / filter dropdowns / mode toggles
  • Command.Body + Command.Sidebar + Command.SidebarItem — two-column layout with a real-tablist nav rail
  • Command.Panel — the scrolling content column for anything interactive
  • Command.Section — heading wrapper; auto-hides when its filterable children all filter out
  • Command.Grid + Command.Card — thumbnail card grid (filter-aware via value/keywords)
  • Command.IconRow + Command.AppIcon — horizontal app logo strip
<Command.Dialog>
  <Command.Input placeholder="Apps, Screens, UI Elements…" />
  <Command.Chips action={<Button variant="ghost" size="xs">Clear all</Button>}>
    <Command.ChipItem leadingIcon={<Search />} value="transactions">transactions</Command.ChipItem>
  </Command.Chips>
  <Command.Body>
    <Command.Sidebar value={view} onValueChange={setView}>
      <Command.SidebarItem value="trending" leadingIcon={<TrendingUp />}>Trending</Command.SidebarItem>
      <Command.SidebarItem value="screens"  leadingIcon={<Smartphone />}>Screens</Command.SidebarItem>
    </Command.Sidebar>
    <Command.Panel>
      <Command.IconRow>
        <Command.AppIcon src="/duolingo.png" label="Duolingo" />
      </Command.IconRow>
      <Command.Section heading="Screens">
        <Command.Grid columns={3}>
          <Command.Card value="signup" title="Signup" image="/signup.png" />
        </Command.Grid>
      </Command.Section>
    </Command.Panel>
  </Command.Body>
</Command.Dialog>

Accessibility

  • The input is the combobox: aria-expanded="true", aria-controls → the list, aria-autocomplete="list" (the APG trio for an always-open inline combobox). Arrow keys move the highlight via aria-activedescendant, Enter selects, typing filters.
  • Command.List is role="listbox" and owns only options and groups. Everything else the palette renders there — Empty, Error, the Loading row, separators — is presentational; interactive rich content goes in Command.Panel.
  • Async announcements come from a root-owned, always-mounted role="status" region outside the listbox: Command.Loading/Command.Error portal their labels into it, and the palette announces "N results" 150ms after each query settles.
  • Esc clears a filled input first; a second Esc (field empty) reaches the Dialog and closes it.
  • The dialog traps focus (@base-ui/react/dialog) and stays a centered card below md, offset by the top safe area and capped so nothing hides behind a virtual keyboard; initialFocus opts out of input autofocus where a keyboard would occlude content.
  • Chips rows and the sidebar are single Tab stops with arrow-key roving; a chip with onRemove deletes with Delete/Backspace; the sidebar is a real vertical tablist.
  • Phone floors: input text ≥16px (no iOS zoom-on-focus), rows/chips/sidebar items/app icons ≥44px targets, the clear button's hit area is extended to 44px.
ComboboxContainer

On this page

PlaygroundInstallationUsageInline command paletteModal command paletteItems with rich contentExamplesDefaultModal dialogMobile presentationAnchoredScope toolbarMatch highlightingAsync searchActionable empty stateRecents chipsFilteringPinned AI rowNested pagesRich content layoutRich itemsSizesRoundedIntentStatesAPI ReferenceCommand.Root / Command.Dialog propsCommand.Dialog additional propsCommand.Input propsCommand.ToolbarCommand.Item propsCommand.ItemMetaCommand.HighlightMatch propsCommand.Group propsCommand.Crumbs propsCommand.Empty propsCommand.Loading propsCommand.Error propsCommand.Chips / Command.ChipItem propsCommand.Body / Command.Sidebar propsCommand.Panel propsCommand.Section propsCommand.Grid / Command.Card propsCommand.IconRow / Command.AppIcon propsuseCommandShortcut(key, onTrigger, options?)Rich content layoutsAccessibility