# Command Menu with Documentation Search

> A command menu with documentation search block.

A shadcn/ui block from the [@blocks-so](https://blocks.so) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/blocks-so/command-menu-03).

- Page, with a live preview: https://shadcn.exchange/r/blocks-so/command-menu-03
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/blocks-so/command-menu-03-light-b1f72fafcb554d2f.png
- Registry item: https://blocks.so/r/command-menu-03.json
- Categories: command-menu
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://blocks.so/r/command-menu-03.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@tabler/icons-react`
- Registry items: `button`, `command`, `dialog`, `kbd`

## Files

### `content/components/command-menu/command-menu-03.tsx`

```tsx
'use client';

import { IconArrowRight, IconCornerDownLeft } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from '@/components/ui/command';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Kbd } from '@/components/ui/kbd';

const navItems = [
  { href: '/', label: 'Home', keywords: ['home', 'main', 'index'] },
  {
    href: '/dashboard',
    label: 'Dashboard',
    keywords: ['dashboard', 'overview'],
  },
  {
    href: '/settings',
    label: 'Settings',
    keywords: ['settings', 'preferences'],
  },
];

const pageGroups = [
  {
    name: 'Getting Started',
    pages: [
      {
        name: 'Introduction',
        href: '/docs/introduction',
        keywords: ['intro', 'start'],
      },
      {
        name: 'Installation',
        href: '/docs/installation',
        keywords: ['install', 'setup'],
      },
      {
        name: 'Quick Start',
        href: '/docs/quick-start',
        keywords: ['quick', 'begin'],
      },
    ],
  },
  {
    name: 'Utilities',
    pages: [
      {
        name: 'Typography',
        href: '/docs/utilities/typography',
        keywords: ['text', 'font'],
      },
      {
        name: 'Colors',
        href: '/docs/utilities/colors',
        keywords: ['color', 'theme'],
      },
      {
        name: 'Spacing',
        href: '/docs/utilities/spacing',
        keywords: ['margin', 'padding'],
      },
    ],
  },
];

const colorGroups = [
  {
    name: 'Neutral',
    colors: [
      {
        name: 'Neutral 50',
        className: 'neutral-50',
        value: 'oklch(0.985 0 0)',
      },
      {
        name: 'Neutral 100',
        className: 'neutral-100',
        value: 'oklch(0.97 0 0)',
      },
      {
        name: 'Neutral 200',
        className: 'neutral-200',
        value: 'oklch(0.922 0 0)',
      },
      {
        name: 'Neutral 500',
        className: 'neutral-500',
        value: 'oklch(0.556 0 0)',
      },
      {
        name: 'Neutral 900',
        className: 'neutral-900',
        value: 'oklch(0.205 0 0)',
      },
    ],
  },
  {
    name: 'Blue',
    colors: [
      {
        name: 'Blue 50',
        className: 'blue-50',
        value: 'oklch(0.97 0.014 254.604)',
      },
      {
        name: 'Blue 500',
        className: 'blue-500',
        value: 'oklch(0.623 0.214 259.815)',
      },
      {
        name: 'Blue 600',
        className: 'blue-600',
        value: 'oklch(0.546 0.245 262.881)',
      },
    ],
  },
];

export function CommandMenu03() {
  const router = useRouter();
  const [open, setOpen] = useState(true);

  const copyToClipboard = useCallback((text: string) => {
    navigator.clipboard.writeText(text);
  }, []);

  const runCommand = useCallback((command: () => unknown) => {
    setOpen(false);
    command();
  }, []);

  useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if ((e.key === 'k' && (e.metaKey || e.ctrlKey)) || e.key === '/') {
        if (
          (e.target instanceof HTMLElement && e.target.isContentEditable) ||
          e.target instanceof HTMLInputElement ||
          e.target instanceof HTMLTextAreaElement ||
          e.target instanceof HTMLSelectElement
        ) {
          return;
        }
        e.preventDefault();
        setOpen((prev) => !prev);
      }
    };

    document.addEventListener('keydown', down);
    return () => document.removeEventListener('keydown', down);
  }, []);

  return (
    <>
      <Button onClick={() => setOpen(true)} variant="outline">
        Open Command Menu
      </Button>

      <Dialog onOpenChange={setOpen} open={open}>
        <DialogContent className="rounded-xl border-none bg-clip-padding p-2 pb-11 shadow-2xl ring-4 ring-neutral-200/80 **:data-[slot=dialog-close]:top-3! **:data-[slot=dialog-close]:right-3! sm:max-w-md dark:bg-neutral-900 dark:ring-neutral-800">
          <DialogHeader className="sr-only">
            <DialogTitle>Search documentation...</DialogTitle>
            <DialogDescription>
              Search for a command to run...
            </DialogDescription>
          </DialogHeader>

          <Command className="rounded-none! bg-transparent p-0 **:data-[slot=input-group]:h-9! **:data-[slot=input-group]:rounded-md! **:data-[slot=input-group]:border-input **:data-[slot=input-group]:bg-input/50 **:data-[slot=command-input-wrapper]:p-0 **:data-[slot=command-input]:pr-9 **:data-[slot=command-input]:pl-2! **:data-[slot=input-group-addon]:pl-3!">
            <CommandInput placeholder="Search documentation..." />
            <CommandList className="no-scrollbar min-h-80 scroll-pt-2 scroll-pb-1.5">
              <CommandEmpty className="py-12 text-center text-muted-foreground text-sm">
                No results found.
              </CommandEmpty>

              {navItems.length > 0 && (
                <CommandGroup
                  className="p-0! **:[[cmdk-group-heading]]:scroll-mt-16 **:[[cmdk-group-heading]]:p-3! **:[[cmdk-group-heading]]:pb-1!"
                  heading="Pages"
                >
                  {navItems.map((item) => (
                    <CommandItem
                      className="h-9 rounded-md border border-transparent px-3! font-medium hover:border-input hover:bg-input/50"
                      key={item.href}
                      keywords={item.keywords}
                      onSelect={() => {
                        runCommand(() => router.push(item.href));
                      }}
                      value={`Navigation ${item.label}`}
                    >
                      <IconArrowRight aria-hidden="true" className="size-4" />
                      {item.label}
                    </CommandItem>
                  ))}
                </CommandGroup>
              )}

              {pageGroups.map((group) => (
                <CommandGroup
                  className="p-0! **:[[cmdk-group-heading]]:scroll-mt-16 **:[[cmdk-group-heading]]:p-3! **:[[cmdk-group-heading]]:pb-1!"
                  heading={group.name}
                  key={group.name}
                >
                  {group.pages.map((page) => {
                    const isComponent = page.href.includes('/components/');
                    return (
                      <CommandItem
                        className="h-9 rounded-md border border-transparent px-3! font-medium hover:border-input hover:bg-input/50"
                        key={page.href}
                        keywords={page.keywords}
                        onSelect={() => {
                          runCommand(() => router.push(page.href));
                        }}
                        value={`${group.name} ${page.name}`}
                      >
                        {isComponent ? (
                          <div className="aspect-square size-4 rounded-full border border-muted-foreground border-dashed" />
                        ) : (
                          <IconArrowRight
                            aria-hidden="true"
                            className="size-4"
                          />
                        )}
                        {page.name}
                      </CommandItem>
                    );
                  })}
                </CommandGroup>
              ))}

              {colorGroups.map((colorGroup) => (
                <CommandGroup
                  className="p-0! **:[[cmdk-group-heading]]:p-3!"
                  heading={colorGroup.name}
                  key={colorGroup.name}
                >
                  {colorGroup.colors.map((color) => (
                    <CommandItem
                      className="h-9 rounded-md border border-transparent px-3! font-medium hover:border-input hover:bg-input/50"
                      key={color.className}
                      keywords={['color', color.name, color.className]}
                      onSelect={() => {
                        runCommand(() => copyToClipboard(color.value));
                      }}
                      value={color.className}
                    >
                      <div
                        className="aspect-square size-4 rounded-sm border"
                        style={{ backgroundColor: color.value }}
                      />
                      {color.className}
                      <CommandShortcut className="font-mono font-normal text-xs tabular-nums tracking-normal">
                        {color.value}
                      </CommandShortcut>
                    </CommandItem>
                  ))}
                </CommandGroup>
              ))}
            </CommandList>
          </Command>

          <div className="absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 font-medium text-muted-foreground text-xs dark:border-t-neutral-700 dark:bg-neutral-800">
            <Kbd>
              <IconCornerDownLeft aria-hidden="true" className="size-3" />
            </Kbd>
            Select
          </div>
        </DialogContent>
      </Dialog>
    </>
  );
}
```
