Skip to content

Command

A searchable list of actions or options: command menus, pickers and quick switchers. Built on cmdk, with groups, shortcuts, disabled items and an empty state. CK-UI's own ⌘K docs search is built with it, and so is Combobox.

Use ↑ ↓ and Enter, or click an item.

Installation

Add Command to your project with the CK-UI CLI. It copies the source into your project, so the code is yours to change.

Terminal
npx ck-ui-cli@latest add command
Adds
Command.tsxto your CK-UI folder (src/components/ck-ui by default)
Installs
cmdkreact-iconsif your project does not have them yet

First time with CK-UI in this project? Run npx ck-ui-cli@latest init once before adding components. The installation guide explains what it sets up.

Manual installationInstall the packages and copy the files yourself.

Install the following dependencies

Run these commands in your terminal to install the required packages

Terminal
npm install cmdk react-icons

Create or update your Command component

Copy this code to create your Command component

/components/ui/Command.tsx
"use client";

import * as React from "react";
import { Command as CommandPrimitive } from "cmdk";
import { RiSearchLine } from "react-icons/ri";
import { cn } from "@/lib/utils";

/*
 * A searchable list of actions or options, built on cmdk:
 *   Command.Input  → role="combobox" controlling the list (aria-activedescendant, aria-expanded)
 *   Command.List   → role="listbox"; Command.Item → role="option" (aria-selected marks the highlighted item)
 * Arrow keys move the highlight, Enter selects, Home/End jump. Filtering is built in (set shouldFilter={false}
 * to filter yourself). It is a generic primitive: it knows nothing about routing or CK-UI's docs.
 */

export type CommandRootProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive>;

/** `label` names the list for assistive technology. */
const Root = React.forwardRef<React.ElementRef<typeof CommandPrimitive>, CommandRootProps>(({ className, ...props }, ref) => (
    <CommandPrimitive
        ref={ref}
        className={cn(
            "flex w-full flex-col overflow-hidden bg-surface text-surface-foreground",
            "[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:pt-2 [&_[cmdk-group-heading]]:size-subheading-2xs [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:text-muted-foreground",
            className
        )}
        {...props}
    />
));
Root.displayName = "Command.Root";

export interface CommandInputProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> {
    /** Classes for the row around the input (icon + input + children). */
    wrapperClassName?: string;
    /** Hide the leading search icon. */
    hideIcon?: boolean;
    /** Rendered after the input, e.g. a keyboard hint. */
    children?: React.ReactNode;
}

const Input = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Input>, CommandInputProps>(({ className, wrapperClassName, hideIcon = false, children, ...props }, ref) => (
    <div className={cn("flex items-center gap-2 border-b border-border px-3", wrapperClassName)}>
        {!hideIcon && <RiSearchLine aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />}
        <CommandPrimitive.Input
            ref={ref}
            className={cn(
                "flex h-11 w-full border-none bg-transparent p-0 size-p-sm text-foreground outline-none placeholder:text-muted-foreground focus:ring-0",
                "disabled:cursor-not-allowed disabled:opacity-50",
                className
            )}
            {...props}
        />
        {children}
    </div>
));
Input.displayName = "Command.Input";

const List = React.forwardRef<React.ElementRef<typeof CommandPrimitive.List>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>>(({ className, ...props }, ref) => (
    <CommandPrimitive.List ref={ref} className={cn("max-h-80 overflow-y-auto overflow-x-hidden p-1.5", className)} {...props} />
));
List.displayName = "Command.List";

const Empty = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Empty>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>>(({ className, ...props }, ref) => (
    <CommandPrimitive.Empty ref={ref} className={cn("py-6 text-center size-p-sm text-muted-foreground", className)} {...props} />
));
Empty.displayName = "Command.Empty";

const Loading = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Loading>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Loading>>(({ className, ...props }, ref) => (
    <CommandPrimitive.Loading ref={ref} className={cn("py-6 text-center size-p-sm text-muted-foreground", className)} {...props} />
));
Loading.displayName = "Command.Loading";

/** `heading` is shown above the group and names it for assistive technology. */
const Group = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Group>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>>(({ className, ...props }, ref) => (
    <CommandPrimitive.Group ref={ref} className={cn("overflow-hidden py-1", className)} {...props} />
));
Group.displayName = "Command.Group";

const Separator = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Separator>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>>(({ className, ...props }, ref) => (
    // cmdk gives it role="separator", which a listbox may not contain; it is decorative (each group has a heading).
    <CommandPrimitive.Separator ref={ref} aria-hidden="true" className={cn("-mx-1.5 my-1 h-px bg-border", className)} {...props} />
));
Separator.displayName = "Command.Separator";

/**
 * An option. `onSelect` runs on Enter or click. `value` is the item's identity and is searched together with
 * `keywords`; give items with the same text a unique `value`.
 */
const Item = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Item>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>>(({ className, ...props }, ref) => (
    <CommandPrimitive.Item
        ref={ref}
        className={cn(
            "relative flex min-h-9 cursor-pointer select-none items-center gap-2 rounded-lg px-2 py-1.5 size-p-sm text-foreground outline-none",
            "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground",
            "data-[disabled=true]:pointer-events-none data-[disabled=true]:text-muted-foreground data-[disabled=true]:opacity-60",
            "[&>svg]:size-4 [&>svg]:shrink-0",
            className
        )}
        {...props}
    />
));
Item.displayName = "Command.Item";

/** Keyboard shortcut hint at the end of an item. Decorative: put the shortcut in `aria-keyshortcuts` if it is real. */
const Shortcut = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(({ className, ...props }, ref) => (
    <span ref={ref} aria-hidden="true" className={cn("ml-auto font-mono size-p-xs tracking-wider text-muted-foreground", className)} {...props} />
));
Shortcut.displayName = "Command.Shortcut";

export { Root, Input, List, Empty, Loading, Group, Separator, Item, Shortcut };

Test Your Component

  • 1. Add the component to a test page
  • 2. Check all props and variants work as expected
  • 3. Verify responsive behavior
  • 4. Test with different content/data
  • 5. Ensure accessibility features work correctly

In a Popover

Compose Command with Popover (or Modal) for pickers and palettes. The search box gets focus when the popup opens.

Custom Filtering

Set shouldFilter={false} and render the items you want, for example results from a search request. Pass filter instead to keep built-in filtering with your own scoring.

v2.4.0 — Command & Combobox
v2.3.1 — Toast fixes
v2.3.0 — Alert Dialog
v2.2.0 — Field
v2.1.0 — Tokens

Accessibility

  • Command.Input is a combobox that controls the listbox (Command.List); it is named by label on Root. Focus stays in the input, and aria-activedescendant points at the highlighted option.
  • Arrow keys move the highlight, Home/End jump to the first/last item, Enter runs the highlighted item. Disabled items are skipped.
  • Each Command.Group is a group named by its heading. Command.Shortcut is decorative; put real shortcuts in aria-keyshortcuts on the element that handles them.
  • The highlight uses the accent token in both themes and is not conveyed by color alone (aria-selected).

API Reference

Styled parts over cmdk; their other props are cmdk's. Import with import * as Command from "@/components/ui/Command".

Command.Root

PropTypeDefaultDescription
label"string""undefined"

Accessible name of the search box and list.

value / defaultValue / onValueChange"string""first item"

The highlighted item's value.

shouldFilter"boolean""true"

Set false to filter yourself.

filter"(value, search, keywords) => number""fuzzy score"

Custom ranking (0 hides the item).

loop"boolean""false"

Wrap around at the ends.

className"string""undefined"

Additional classes, merged last.

Command.Input

PropTypeDefaultDescription
value / onValueChange"string""undefined"

Controlled search text.

wrapperClassName"string""undefined"

Classes for the row around the input.

hideIcon"boolean""false"

Hide the search icon.

children"ReactNode""undefined"

Rendered after the input (e.g. a keyboard hint).

className"string""undefined"

Classes for the input.

Command.List

The scrollable listbox.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

Command.Empty

Shown when no item matches.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

Command.Group

PropTypeDefaultDescription
heading"ReactNode""undefined"

Visible group title; names the group.

className"string""undefined"

Additional classes, merged last.

Command.Item

PropTypeDefaultDescription
value"string""text content"

Identity and search text. Make it unique.

keywords"string[]""undefined"

Extra search terms.

onSelect"(value: string) => void""undefined"

Runs on Enter or click.

disabled"boolean""false"

Skipped by the keyboard; cannot be selected.

className"string""undefined"

Additional classes, merged last.

Command.Separator

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

Command.Shortcut

Decorative shortcut hint at the end of an item.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

Command.Loading

Shown while you load async items.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.