Navigation

Command.

A searchable command palette with grouped items and shortcuts, inline or in a dialog opened with Cmd K.

Installation#

With the CLI

$npx lanterncn add command

This also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/command. See Installation if your project is not set up yet.

Manually

  1. Install the dependencies

    npm install cmdk lucide-react
  2. Copy the source into your project

    components/ui/command.tsx
    "use client";
    
    import * as React from "react";
    import { Command as CommandPrimitive } from "cmdk";
    import { SearchIcon } from "lucide-react";
    
    import { cn } from "@/lib/utils";
    import {
      Dialog,
      DialogContent,
      DialogDescription,
      DialogHeader,
      DialogTitle,
    } from "@/components/ui/dialog";
    
    function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
      return (
        <CommandPrimitive
          data-slot="command"
          className={cn(
            "flex h-full w-full flex-col overflow-hidden rounded-lg bg-popover text-popover-foreground",
            className,
          )}
          {...props}
        />
      );
    }
    
    function CommandDialog({
      title = "Command palette",
      description = "Search for a command to run.",
      children,
      className,
      showCloseButton = false,
      ...props
    }: React.ComponentProps<typeof Dialog> & {
      title?: string;
      description?: string;
      className?: string;
      showCloseButton?: boolean;
    }) {
      return (
        <Dialog {...props}>
          <DialogContent
            className={cn("top-[20%] translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-lg", className)}
            showCloseButton={showCloseButton}
          >
            <DialogHeader className="sr-only">
              <DialogTitle>{title}</DialogTitle>
              <DialogDescription>{description}</DialogDescription>
            </DialogHeader>
            <Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-item]]:py-2.5">{children}</Command>
          </DialogContent>
        </Dialog>
      );
    }
    
    function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
      return (
        <div data-slot="command-input-wrapper" className="flex h-11 items-center gap-2.5 border-b px-3">
          <SearchIcon className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
          <CommandPrimitive.Input
            data-slot="command-input"
            className={cn(
              "flex h-full w-full min-w-0 bg-transparent py-3 text-sm text-foreground outline-hidden placeholder:text-muted-foreground/70 disabled:cursor-not-allowed disabled:opacity-50",
              className,
            )}
            {...props}
          />
        </div>
      );
    }
    
    function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
      return (
        <CommandPrimitive.List
          data-slot="command-list"
          className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
          {...props}
        />
      );
    }
    
    function CommandEmpty({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
      return (
        <CommandPrimitive.Empty
          data-slot="command-empty"
          className={cn(
            "py-8 text-center font-mono text-[10px] tracking-[0.2em] text-muted-foreground uppercase",
            className,
          )}
          {...props}
        />
      );
    }
    
    function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
      return (
        <CommandPrimitive.Group
          data-slot="command-group"
          className={cn(
            "overflow-hidden p-1 text-foreground",
            "[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pt-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:font-mono [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:tracking-[0.2em] [&_[cmdk-group-heading]]:text-success [&_[cmdk-group-heading]]:uppercase",
            className,
          )}
          {...props}
        />
      );
    }
    
    function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
      return (
        <CommandPrimitive.Separator
          data-slot="command-separator"
          className={cn("-mx-1 h-px bg-border", className)}
          {...props}
        />
      );
    }
    
    function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
      return (
        <CommandPrimitive.Item
          data-slot="command-item"
          className={cn(
            "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-[13px] outline-hidden select-none",
            "data-[selected=true]:bg-secondary data-[selected=true]:text-foreground data-[selected=true]:shadow-[inset_2px_0_0_var(--primary)]",
            "data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-40",
            "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[selected=true]:[&_svg:not([class*='text-'])]:text-primary",
            className,
          )}
          {...props}
        />
      );
    }
    
    function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
      return (
        <span
          data-slot="command-shortcut"
          className={cn("ml-auto pl-4 font-mono text-[10px] tracking-[0.12em] text-muted-foreground uppercase", className)}
          {...props}
        />
      );
    }
    
    export {
      Command,
      CommandDialog,
      CommandInput,
      CommandList,
      CommandEmpty,
      CommandGroup,
      CommandItem,
      CommandShortcut,
      CommandSeparator,
    };
  3. Update the import paths to match your project

    The source imports cn from @/lib/utils and uses dialog.

Usage#

import {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
  CommandShortcut,
  CommandSeparator,
} from "@/components/ui/command";
<Command>
  <CommandInput placeholder="Search..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Turtles">
      <CommandItem>Wake turtle 07</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>

Arrow keys move the selection and Enter runs the item. CommandDialog wraps the palette in the Lantern dialog; wire up your own Cmd K / Ctrl K listener to toggle it.

Examples#

Dialog

Or press Cmd K / Ctrl K