# Palette With Preview Pane

> Inline two-column command palette with a searchable grouped result list beside a live preview pane for the highlighted item and its primary action.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/command-palette-4).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/command-palette-4
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/command-palette-4-light-11a6a9106509de12.png
- Registry item: https://7ovr.com/r/command-palette-4.json
- Categories: command-palette
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/command-palette-4.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: `@base-ui/react`
- Registry items: `badge`, `button`, `command`, `kbd`

## Files

### `registry/blocks/command-palette/4/command-palette-block.tsx`

```tsx
"use client"

import * as React from "react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

type IconProps = { className?: string; size?: number | string }

type IconRenderer = (props: IconProps) => React.ReactNode

type Entry = {
  id: string
  icon: IconRenderer
  title: string
  group: string
  category: string
  description: string
  meta: { label: string; value: string }[]
}

const entries: Entry[] = [
  {
    id: "roadmap",
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="FileText"
        tabler="IconFileText"
        hugeicons="File01Icon"
        phosphor="FileText"
        remixicon="RiFileTextLine"
        {...p}
      />
    ),
    title: "Q3 Product Roadmap",
    group: "Documents",
    category: "Document",
    description:
      "The planning doc for the next quarter, covering themes, bets, and the sequencing across teams.",
    meta: [
      { label: "Owner", value: "Mara Lin" },
      { label: "Updated", value: "Jul 9, 2026" },
    ],
  },
  {
    id: "assets",
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Folder"
        tabler="IconFolder"
        hugeicons="FolderIcon"
        phosphor="Folder"
        remixicon="RiFolderLine"
        {...p}
      />
    ),
    title: "Brand Assets",
    group: "Documents",
    category: "Folder",
    description:
      "Logos, color tokens, and export-ready templates for the marketing and product teams.",
    meta: [
      { label: "Items", value: "148 files" },
      { label: "Updated", value: "Jul 2, 2026" },
    ],
  },
  {
    id: "analytics",
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="BarChart"
        tabler="IconChartBar"
        hugeicons="BarChartIcon"
        phosphor="ChartBar"
        remixicon="RiBarChartLine"
        {...p}
      />
    ),
    title: "View Analytics",
    group: "Navigation",
    category: "Page",
    description:
      "Traffic, conversion, and retention dashboards for the current billing period.",
    meta: [
      { label: "Section", value: "Insights" },
      { label: "Access", value: "Admins" },
    ],
  },
  {
    id: "members",
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Users"
        tabler="IconUsersGroup"
        hugeicons="UserGroupIcon"
        phosphor="UsersThree"
        remixicon="RiTeamLine"
        {...p}
      />
    ),
    title: "Browse Members",
    group: "Navigation",
    category: "Page",
    description:
      "The full team directory with roles, status, and their most recent activity.",
    meta: [
      { label: "Section", value: "People" },
      { label: "Count", value: "42 members" },
    ],
  },
  {
    id: "profile",
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="User"
        tabler="IconUser"
        hugeicons="UserIcon"
        phosphor="User"
        remixicon="RiUserLine"
        {...p}
      />
    ),
    title: "Edit Profile",
    group: "Actions",
    category: "Action",
    description:
      "Update your display name, avatar, and the notifications you receive.",
    meta: [{ label: "Shortcut", value: "Cmd E" }],
  },
  {
    id: "settings",
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Settings"
        tabler="IconSettings"
        hugeicons="SettingsIcon"
        phosphor="GearSix"
        remixicon="RiSettings3Line"
        {...p}
      />
    ),
    title: "Open Settings",
    group: "Actions",
    category: "Action",
    description:
      "Workspace preferences, billing, integrations, and security controls.",
    meta: [{ label: "Shortcut", value: "Cmd ," }],
  },
]

const groups = ["Navigation", "Documents", "Actions"]

const byId = Object.fromEntries(entries.map((e) => [e.id, e]))

const COMMAND_GROUP_CLASS =
  "p-1.5 **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:tracking-wide"

function PreviewPane({ entry }: { entry: Entry | undefined }) {
  if (!entry) {
    return (
      <div className="flex h-full items-center justify-center p-6 text-center text-sm text-muted-foreground">
        Select a result to preview it.
      </div>
    )
  }
  return (
    <div className="flex h-full flex-col p-5">
      <div className="flex size-11 items-center justify-center rounded-lg border border-border bg-muted text-foreground">
        <entry.icon className="size-5" aria-hidden="true" />
      </div>
      <div className="mt-4 flex items-center gap-2">
        <h3 className="font-heading text-sm font-semibold">{entry.title}</h3>
        <Badge variant="secondary">{entry.category}</Badge>
      </div>
      <p className="mt-2 text-sm text-pretty text-muted-foreground">
        {entry.description}
      </p>
      <dl className="mt-5 flex flex-col gap-2 border-t border-border pt-4">
        {entry.meta.map((row) => (
          <div key={row.label} className="flex items-center justify-between">
            <dt className="text-xs text-muted-foreground">{row.label}</dt>
            <dd className="text-xs font-medium tabular-nums">{row.value}</dd>
          </div>
        ))}
      </dl>
      <Button className="mt-auto w-full">
        Open
        <KbdGroup>
          <Kbd>
            <IconPlaceholder
              lucide="CornerDownLeft"
              tabler="IconCornerDownLeft"
              hugeicons="CornerDownLeftIcon"
              phosphor="ArrowElbowDownLeft"
              remixicon="RiCornerDownLeftLine"
              className="size-3"
              aria-hidden="true"
            />
          </Kbd>
        </KbdGroup>
      </Button>
    </div>
  )
}

export default function CommandPaletteBlock() {
  const [value, setValue] = React.useState(entries[0].id)
  const selected = byId[value]

  return (
    <section className="flex min-h-svh w-full items-center justify-center bg-background px-6 py-16 text-foreground">
      <div className="grid h-[420px] w-full max-w-2xl grid-cols-1 overflow-hidden rounded-lg border border-border bg-popover sm:grid-cols-[1fr_260px]">
        <Command
          value={value}
          onValueChange={setValue}
          className="bg-transparent"
        >
          <CommandInput
            aria-label="Search everything"
            placeholder="Search everything…"
            className="text-sm"
          />
          <CommandList className="max-h-none flex-1">
            <CommandEmpty className="text-sm">No results found.</CommandEmpty>
            {groups.map((group) => (
              <CommandGroup
                key={group}
                heading={group}
                className={COMMAND_GROUP_CLASS}
              >
                {entries
                  .filter((entry) => entry.group === group)
                  .map((entry) => (
                    <CommandItem
                      key={entry.id}
                      value={entry.id}
                      onSelect={() => setValue(entry.id)}
                      className="gap-2.5 px-3 py-2.5 text-sm"
                    >
                      <entry.icon aria-hidden="true" />
                      <span className="flex-1 truncate">{entry.title}</span>
                    </CommandItem>
                  ))}
              </CommandGroup>
            ))}
          </CommandList>
        </Command>

        <div className="hidden border-l border-border bg-background sm:block">
          <PreviewPane entry={selected} />
        </div>
      </div>
    </section>
  )
}
```
