Navigation
Command.
A searchable command palette with grouped items and shortcuts, inline or in a dialog opened with Cmd K.
No results found.
quarry.hub
Guestbook
Rednet relay
Wake turtle 07Ctrl W
Open shell on server-02Ctrl T
Reboot mainframe
ProfileShift P
SettingsCtrl ,
import {
BookOpenIcon,
BotIcon,
CpuIcon,
GlobeIcon,
RadioTowerIcon,
SettingsIcon,
TerminalIcon,
UserIcon,
} from "lucide-react";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
export default function CommandDemo() {
return (
<Command className="w-full max-w-md border shadow-block-sm">
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Jump to">
<CommandItem>
<GlobeIcon />
quarry.hub
</CommandItem>
<CommandItem>
<BookOpenIcon />
Guestbook
</CommandItem>
<CommandItem>
<RadioTowerIcon />
Rednet relay
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Computers">
<CommandItem>
<BotIcon />
Wake turtle 07
<CommandShortcut>Ctrl W</CommandShortcut>
</CommandItem>
<CommandItem>
<TerminalIcon />
Open shell on server-02
<CommandShortcut>Ctrl T</CommandShortcut>
</CommandItem>
<CommandItem disabled>
<CpuIcon />
Reboot mainframe
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Account">
<CommandItem>
<UserIcon />
Profile
<CommandShortcut>Shift P</CommandShortcut>
</CommandItem>
<CommandItem>
<SettingsIcon />
Settings
<CommandShortcut>Ctrl ,</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
);
}Installation#
With the CLI
$npx lanterncn add commandThis 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
Install the dependencies
npm install cmdk lucide-reactCopy 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, };Update the import paths to match your project
The source imports
cnfrom@/lib/utilsand 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
"use client";
import * as React from "react";
import { BookOpenIcon, BotIcon, GlobeIcon, SearchIcon, SettingsIcon, TerminalIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
export default function CommandDialogDemo() {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
setOpen((value) => !value);
}
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
const run = () => setOpen(false);
return (
<div className="flex flex-col items-center gap-3">
<Button variant="secondary" onClick={() => setOpen(true)} className="w-64 justify-between text-muted-foreground">
<span className="flex items-center gap-2">
<SearchIcon />
Search the hub...
</span>
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
</Button>
<p className="font-mono text-[10px] tracking-[0.2em] text-muted-foreground uppercase">
Or press Cmd K / Ctrl K
</p>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Search pages, turtles, servers..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Pages">
<CommandItem onSelect={run}>
<GlobeIcon />
Directory
</CommandItem>
<CommandItem onSelect={run}>
<BookOpenIcon />
Guestbook
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem onSelect={run}>
<BotIcon />
Send turtle home
<CommandShortcut>Ctrl H</CommandShortcut>
</CommandItem>
<CommandItem onSelect={run}>
<TerminalIcon />
Open terminal
<CommandShortcut>Ctrl T</CommandShortcut>
</CommandItem>
<CommandItem onSelect={run}>
<SettingsIcon />
Hub settings
<CommandShortcut>Ctrl ,</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</div>
);
}