# CommandPalette

> Spotlight-style ⌘K palette that ranks and renders every command registered in the app, with nested pages, async results and a SearchTrigger field.

Source: https://docs.nasaqui.com/components/command-palette

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/command-palette.json
```

The UI half of Nasaq's command system. It holds **no commands of its own**: it reads what components
registered through the [commands registry](https://docs.nasaqui.com/components/commands) (`useRegisterCommands`,
`useRegisterCommandSource`), ranks them against the query, groups them by section and renders them.
This is how a product adds navigation, creation, contextual and AI actions without Nasaq knowing any
business logic: the product passes plain data plus a `perform` callback.

- Opens with `⌘K` / `Ctrl+K` (or `SearchTrigger`).
- Diacritic- and letter-variant-insensitive Arabic search.
- Commands with `children` open a nested page; `Backspace` on an empty field goes back.
- Async sources add results as the user types (debounced, abortable).
- Also exports `SearchTrigger`, the "Search… ⌘K" field for the sidebar.

## When to use

- Global search and quick actions in a product with many destinations and commands.
- Exposing keyboard shortcuts (`G I`, `C`) with a discoverable list.

## When not to use

- Filtering one list or table: use that component's own search field.
- A picker inside a form: use a select or combobox field.
- Storing business logic in Nasaq: register commands from the product instead.

## Import

```tsx
import { CommandPalette, SearchTrigger, type Command, type CommandPaletteProps } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

Inside `AppShell` (which supplies the registry and shared open state):

```tsx
import { AppShell, CommandPalette, SearchTrigger, Sidebar, SidebarHeader, useRegisterCommands, type Command } from "@fadymondy/nasaq/web";
import { FilePlus2, Inbox } from "lucide-react";
import { useMemo } from "react";

function Palette() {
  const commands = useMemo<Command[]>(
    () => [
      { id: "app.go.inbox", section: "navigation", label: "Inbox", icon: Inbox, shortcut: "G I", perform: () => location.assign("/inbox") },
      { id: "app.new.issue", section: "create", label: "New issue", icon: FilePlus2, shortcut: "C", perform: () => console.log("new issue") },
    ],
    [],
  );
  useRegisterCommands(commands);
  return <CommandPalette />;
}

export function Root({ children }: { children: React.ReactNode }) {
  return (
    <AppShell
      sidebar={
        <Sidebar>
          <SidebarHeader>
            <SearchTrigger />
          </SidebarHeader>
        </Sidebar>
      }
    >
      {children}
      <Palette />
    </AppShell>
  );
}
```

Memoise the array you pass to `useRegisterCommands`; a new array each render re-registers every render.

Without `AppShell`, wrap in `CommandProvider` and control the palette with `open` / `onOpenChange`
(see Examples). `SearchTrigger` can only open a palette through the shell; outside it, give it an `onClick`.

## Anatomy

```
CommandPalette                       Dialog, data-slot="command-palette" (aria-label = labels.title)
├─ input row
│  ├─ search icon                    spinner while async sources are running
│  ├─ page chips                     one per nested page; click to jump back
│  ├─ input                          Autocomplete input
│  └─ "Esc" close button
├─ results                           Autocomplete list, scrolls
│  ├─ empty state                    emptyLabel / searching
│  └─ group × n                      heading = section label (screen-reader-only on a nested page)
│     └─ item × n                    icon, label, hint, shortcut keys, chevron when it has children
└─ hint bar                          ↑↓ navigate · ↵ open · ⌫ back (only on a nested page)

SearchTrigger                        data-slot="search-trigger" (button)
```

## API

### `CommandPalette`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `commands?` | `Command[]` | none | Extra commands on top of the registry. |
| `groups?` | `CommandGroup[]` | none | **Deprecated** static groups; they render as their own sections before the registry's. |
| `sectionLabels?` | `Partial<Record<CommandSection \| (string & {}), string>>` | none | Override section headings, e.g. `{ navigation: "Jump to" }`. Standard sections are already localised. |
| `open?` | `boolean` | shell state, else local | Controlled open state. Inside `AppShell` it defaults to the shell's, shared with `SearchTrigger`. |
| `onOpenChange?` | `(open: boolean) => void` | none | Called on every open/close. |
| `hotkey?` | `boolean` | `true` | Registers `⌘K` / `Ctrl+K`. Render only one palette (or set `false` on the others). |
| `placeholder?` | `string` | "Search or run a command…" / "ابحث أو نفّذ أمرًا…" | Input placeholder (root page). |
| `emptyLabel?` | `string` | "No results" / "لا نتائج" | Empty state. |
| `labels?` | `{ title?; navigate?; select?; close?; back?; searching? }` | EN/AR built in | Dialog name ("Command palette"), hint texts ("Navigate", "Open", "Back"), Esc button name ("Close"), "Searching…". |

Each open starts fresh: empty query, root page, no async results. Opening is idempotent per state.

### `CommandItem` and `CommandGroup` (deprecated)

Legacy shapes for static lists: `CommandItem { id; label; icon?: LucideIcon; keywords?; shortcut?; hint?; onSelect? }` and
`CommandGroup { id; label; items: CommandItem[] }`. They map to `Command` with `bindShortcut: false`. Prefer `Command`.

### `SearchTrigger`

Extends `ComponentProps<"button">`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `string` | "Search…" | Field text; also the accessible name and tooltip in compact form. **Localise it.** |
| `variant?` | `"field" \| "icon"` | `"field"` | `"field"` is the sidebar box with `⌘K`; `"icon"` is a header icon button. It is always an icon on the collapsed rail. |
| `onClick?` | `MouseEventHandler` | none | Runs first; call `event.preventDefault()` to stop it opening the palette. |

Inside `AppShell` a click calls `setCommandOpen(true)`. It carries `aria-keyshortcuts="Meta+K Control+K"`.

## Examples

### Async results ("search" section)

```tsx
import { type CommandSource, useRegisterCommandSource } from "@fadymondy/nasaq/web";
import { FileText } from "lucide-react";
import { useMemo } from "react";

export function IssueSearch({ open }: { open: (key: string) => void }) {
  const source = useMemo<CommandSource>(
    () => ({
      id: "issues",
      minQuery: 2,
      debounce: 200,
      search: async (query, signal) => {
        const res = await fetch(`/api/issues?q=${encodeURIComponent(query)}`, { signal });
        const issues: { key: string; title: string }[] = await res.json();
        return issues.map((i) => ({ id: `issue.${i.key}`, label: i.title, hint: i.key, icon: FileText, perform: () => open(i.key) }));
      },
    }),
    [open],
  );
  useRegisterCommandSource(source);
  return null;
}
```

Results default to the `search` section ("Results" / "النتائج") and are not re-filtered by label, so match on the server.

### Nested page ("Change theme…")

```tsx
import { type Command, useNasaq, useRegisterCommands } from "@fadymondy/nasaq/web";
import { Monitor, Moon, Palette, Sun } from "lucide-react";
import { useMemo } from "react";

export function ThemeCommands() {
  const { setTheme } = useNasaq();
  const commands = useMemo<Command[]>(
    () => [
      {
        id: "app.system.theme",
        section: "system",
        label: "Change theme…",
        icon: Palette,
        keywords: ["dark", "light", "مظهر"],
        children: [
          { id: "app.theme.light", label: "Light", icon: Sun, perform: () => setTheme("light") },
          { id: "app.theme.dark", label: "Dark", icon: Moon, perform: () => setTheme("dark") },
          { id: "app.theme.system", label: "System", icon: Monitor, perform: () => setTheme("system") },
        ],
      },
    ],
    [setTheme],
  );
  useRegisterCommands(commands);
  return null;
}
```

### Without AppShell (controlled)

```tsx
import { Button, CommandPalette, CommandProvider } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Standalone() {
  const [open, setOpen] = useState(false);
  return (
    <CommandProvider>
      <Button onClick={() => setOpen(true)}>Open palette</Button>
      <CommandPalette open={open} onOpenChange={setOpen} />
    </CommandProvider>
  );
}
```

### Arabic

```tsx
import { CommandPalette } from "@fadymondy/nasaq/web";

export const Ar = () => (
  <CommandPalette
    placeholder="ابحث أو نفّذ أمرًا…"
    emptyLabel="لا نتائج"
    sectionLabels={{ navigation: "انتقال سريع" }}
    labels={{ title: "لوحة الأوامر", navigate: "تنقّل", select: "فتح", back: "رجوع", close: "إغلاق" }}
  />
);
```

## Accessibility

| Key | Action |
| --- | --- |
| `⌘K` / `Ctrl+K` | Toggle the palette (when `hotkey`). Works from anywhere, including fields. |
| `↑` `↓` | Move the highlight (first result highlighted automatically). |
| `Enter` | Run the highlighted command, or open its nested page. |
| `Backspace` (empty input) | Back one nested page. |
| `Esc` | On a nested page, go back one level; at the root, close the palette. |
| `Tab` | Reaches page chips and the Esc button. |

- The dialog is named by `labels.title`. The input is described by the hint bar; the highlighted item follows Base UI Autocomplete semantics (listbox, `aria-activedescendant`).
- Group headings label the sections; on a nested page they are screen-reader-only.
- The spinner is decorative; "Searching…" is shown in the empty state while a search has no results yet.
- Disabled commands are dimmed and cannot run.
- **Localise:** `placeholder`, `emptyLabel`, `labels`, `sectionLabels` for custom sections, `SearchTrigger.label`, and every command's `label`, `hint` and `keywords`.

## RTL & i18n

- Layout uses logical properties; the chevron on nested commands and page chips is a `directional` icon and flips.
- Shortcut keys are always LTR (`dir="ltr"`), and bound on physical keys, so `G I` works on an Arabic layout.
- Search folds case, diacritics, tatweel and `أإآٱ → ا`, `ى → ي`, `ة → ه`, so "اداره" finds "إدارة". Add the other language's name to `keywords`.
- Built-in strings switch on the provider locale. Standard section headings are localised (see `DEFAULT_SECTION_LABELS`).
- Palette opens `pt-[12dvh]` and input text is 16px on coarse pointers to avoid iOS zoom.

## Styling & tokens

- Surface: `bg-popover`, `shadow-floating`, `rounded-floating`, `border-border`; backdrop `nq-fg`/`nq-bg` overlay.
- Highlight: `data-highlighted:bg-nq-selected`. Hint bar `bg-nq-surface-soft`.
- Target `[data-slot=command-palette]`, `[data-slot=search-trigger]`. Items are Base UI Autocomplete items (`data-highlighted`, `data-disabled`).
- There is no `className` prop on the palette; `SearchTrigger` accepts one.

## Do / Don't

- **Do** register commands from the product with stable ids, prefixed (`"mahaam.issue.new"`).
- **Do** give commands a `section` and put synonyms and other-language names in `keywords`.
- **Do** put rare or destructive commands behind `searchOnly`.
- **Don't** render two palettes with `hotkey` on; `⌘K` would toggle both.
- **Don't** put business logic in Nasaq: pass `perform`.
- **Don't** rely on a shortcut as the only way to reach a command; every command is also in the palette.

## Related

- [commands](https://docs.nasaqui.com/components/commands): the registry, `Command` type, sections, matching, shortcut binding
- [app-shell](https://docs.nasaqui.com/components/app-shell) · [product-switcher](https://docs.nasaqui.com/components/product-switcher) (registers "Switch to…") · [workspace-switcher](https://docs.nasaqui.com/components/workspace-switcher)

## Lab

https://docs.nasaqui.com/?path=/docs/components-keyboard-commands-command-palette--docs

## Code

### React

```tsx
import { AppShell, CommandPalette, SearchTrigger, Sidebar, SidebarHeader, useRegisterCommands, type Command } from "@fadymondy/nasaq/web";
import { FilePlus2, Inbox } from "lucide-react";
import { useMemo } from "react";

function Palette() {
  const commands = useMemo<Command[]>(
    () => [
      { id: "app.go.inbox", section: "navigation", label: "Inbox", icon: Inbox, shortcut: "G I", perform: () => location.assign("/inbox") },
      { id: "app.new.issue", section: "create", label: "New issue", icon: FilePlus2, shortcut: "C", perform: () => console.log("new issue") },
    ],
    [],
  );
  useRegisterCommands(commands);
  return <CommandPalette />;
}

export function Root({ children }: { children: React.ReactNode }) {
  return (
    <AppShell
      sidebar={
        <Sidebar>
          <SidebarHeader>
            <SearchTrigger />
          </SidebarHeader>
        </Sidebar>
      }
    >
      {children}
      <Palette />
    </AppShell>
  );
}
```

### shadcn

```tsx
import { AppShell, Sidebar, SidebarHeader } from "@/components/ui/app-shell";
import { CommandPalette, SearchTrigger } from "@/components/ui/command-palette";
import { useRegisterCommands, type Command } from "@/components/ui/commands";
import { FilePlus2, Inbox } from "lucide-react";
import { useMemo } from "react";

function Palette() {
  const commands = useMemo<Command[]>(
    () => [
      { id: "app.go.inbox", section: "navigation", label: "Inbox", icon: Inbox, shortcut: "G I", perform: () => location.assign("/inbox") },
      { id: "app.new.issue", section: "create", label: "New issue", icon: FilePlus2, shortcut: "C", perform: () => console.log("new issue") },
    ],
    [],
  );
  useRegisterCommands(commands);
  return <CommandPalette />;
}

export function Root({ children }: { children: React.ReactNode }) {
  return (
    <AppShell
      sidebar={
        <Sidebar>
          <SidebarHeader>
            <SearchTrigger />
          </SidebarHeader>
        </Sidebar>
      }
    >
      {children}
      <Palette />
    </AppShell>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { FilePlus2, Inbox } from "lucide-vue-next";
import { defineComponent, h } from "vue";
import {
  NqAppShell,
  NqCommandPalette,
  NqSearchTrigger,
  NqSidebar,
  NqSidebarHeader,
  useRegisterCommands,
  type Command,
} from "@fadymondy/nasaq/vue";

const commands: Command[] = [
  { id: "app.go.inbox", section: "navigation", label: "Inbox", icon: Inbox, shortcut: "G I", perform: () => console.log("inbox") },
  { id: "app.new.issue", section: "create", label: "New issue", icon: FilePlus2, shortcut: "C", perform: () => console.log("new issue") },
];

// Inside NqAppShell, which supplies the command registry and the shared open state. Register from a child of the shell.
const Palette = defineComponent({
  setup() {
    useRegisterCommands(commands);
    return () => h(NqCommandPalette);
  },
});
</script>

<template>
  <NqAppShell class="min-h-96">
    <template #sidebar>
      <NqSidebar>
        <NqSidebarHeader><NqSearchTrigger /></NqSidebarHeader>
      </NqSidebar>
    </template>
    <p class="p-6">Press Ctrl+K or Cmd+K.</p>
    <Palette />
  </NqAppShell>
</template>
```

### Blade

```blade
<div class="flex flex-col gap-4">
    <x-nq::command-palette.search-trigger class="max-w-xs" />
    <x-nq::command-palette hotkey :commands="[
        ['id' => 'new', 'label' => 'New issue', 'section' => 'create', 'icon' => 'plus', 'shortcut' => 'C', 'keywords' => ['add']],
        ['id' => 'inbox', 'label' => 'Go to inbox', 'section' => 'navigation', 'icon' => 'inbox', 'href' => '/inbox', 'hint' => 'Page'],
        ['id' => 'status', 'label' => 'Change status', 'section' => 'context', 'children' => [
            ['id' => 'status.todo', 'label' => 'Todo'],
            ['id' => 'status.done', 'label' => 'Done'],
        ]],
        ['id' => 'danger', 'label' => 'Delete workspace', 'section' => 'system', 'disabled' => true],
    ]" />
</div>
```

### HTML + Alpine

```html
<div class="flex flex-col gap-4">
    <button type="button" data-slot="search-trigger" aria-keyshortcuts="Meta+K Control+K" x-data="nqSearchTrigger" x-on:click="press($event)"
        class="flex h-control min-h-[var(--nq-touch-min,0px)] w-full items-center gap-2 rounded-control border border-border bg-card px-2 text-body-sm text-muted-foreground transition-colors duration-150 ease-nq outline-none hover:border-nq-line-strong hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0 max-w-xs">
    <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m21 21-4.34-4.34"/>
  <circle cx="11" cy="11" r="8"/>
</svg>            <span class="flex-1 truncate text-start">Search…</span>
        <span class="flex gap-0.5" dir="ltr">
            <kbd data-slot="kbd"  dir="ltr"
    x-text="mod" class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">Ctrl</kbd>
            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">K</kbd>
        </span>
    </button>
    <div data-slot="command-palette-root" x-data="nqCommandPalette(JSON.parse('[{\u0022id\u0022:\u0022new\u0022,\u0022label\u0022:\u0022New issue\u0022,\u0022section\u0022:\u0022create\u0022,\u0022shortcut\u0022:\u0022C\u0022,\u0022keywords\u0022:[\u0022add\u0022],\u0022iconHtml\u0022:\u0022\\u003Csvg aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Cpath d=\\u0022M5 12h14\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M12 5v14\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022},{\u0022id\u0022:\u0022inbox\u0022,\u0022label\u0022:\u0022Go to inbox\u0022,\u0022section\u0022:\u0022navigation\u0022,\u0022href\u0022:\u0022\\\/inbox\u0022,\u0022hint\u0022:\u0022Page\u0022,\u0022iconHtml\u0022:\u0022\\u003Csvg aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Cpolyline points=\\u002222 12 16 12 14 15 10 15 8 12 2 12\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022},{\u0022id\u0022:\u0022status\u0022,\u0022label\u0022:\u0022Change status\u0022,\u0022section\u0022:\u0022context\u0022,\u0022children\u0022:[{\u0022id\u0022:\u0022status.todo\u0022,\u0022label\u0022:\u0022Todo\u0022},{\u0022id\u0022:\u0022status.done\u0022,\u0022label\u0022:\u0022Done\u0022}]},{\u0022id\u0022:\u0022danger\u0022,\u0022label\u0022:\u0022Delete workspace\u0022,\u0022section\u0022:\u0022system\u0022,\u0022disabled\u0022:true}]'), {})" x-modelable="open"
    x-on:keydown.window="onHotkey($event)" x-on:nq-command-palette-open.window="show()" x-on:nq-command-palette-toggle.window="toggle()" class="contents">
    <template x-teleport="body">
        <div data-slot="command-palette-portal">
            <div data-slot="command-palette-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/10 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
            <div class="pointer-events-none fixed inset-0 z-50 flex items-start justify-center px-3 pt-[12dvh]">
                <div data-slot="command-palette" role="dialog" aria-modal="true" aria-label="Command palette" tabindex="-1" x-nq-presence="open" x-trap.noscroll="open"
                    x-on:keydown.escape="onEscape($event)"
                    class="pointer-events-auto flex max-h-[min(32rem,76dvh)] w-full max-w-xl flex-col overflow-hidden rounded-floating border border-border bg-popover text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
                    <div class="flex items-center gap-2 border-b border-border px-4">
                        <svg x-show="searching" aria-hidden="true" class="size-4 shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>                        <svg x-show="! searching" aria-hidden="true" class="size-4 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m21 21-4.34-4.34"/>
  <circle cx="11" cy="11" r="8"/>
</svg>                        <template x-for="(p, i) in pages" x-bind:key="p.id">
                            <button type="button" x-on:click="back(i)"
                                class="flex h-6 shrink-0 items-center gap-1 rounded-[4px] bg-nq-selected px-1.5 text-caption font-medium text-foreground outline-none focus-visible:outline-2 focus-visible:outline-nq-focus">
                                <span x-text="p.label"></span>
                                <svg aria-hidden="true" class="size-3 text-muted-foreground rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m9 18 6-6-6-6"/>
</svg>                            </button>
                        </template>
                        <input type="text" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-controls="nq-cp-list" autocomplete="off" spellcheck="false"
                            x-model="query" x-on:keydown="onKey($event)"
                            x-bind:aria-activedescendant="view().flat.length ? optionId(highlighted) : null"
                            x-bind:placeholder="page() ? 'Filter\u2026' : 'Search or run a command\u2026'"
                            class="h-12 w-full bg-transparent text-body text-foreground outline-none placeholder:text-muted-foreground pointer-coarse:text-[16px]">
                        <button type="button" x-on:click="close()" class="shrink-0 rounded-[3px] outline-none focus-visible:outline-2 focus-visible:outline-nq-focus">
                            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">Esc</kbd>
                            <span class="sr-only">Close</span>
                        </button>
                    </div>
                    <div class="min-h-0 flex-1 overflow-y-auto overscroll-contain p-1.5 [scroll-padding-block:0.375rem]">
                        <div x-show="view().flat.length === 0" data-slot="command-palette-empty" class="flex min-h-24 items-center justify-center text-body-sm text-muted-foreground">
                            <span x-show="searching">Searching…</span>
                            <span x-show="! searching">No results</span>
                        </div>
                        <div id="nq-cp-list" role="listbox" aria-label="Command palette">
                            <template x-for="section in view().sections" x-bind:key="section.id">
                                <div role="group" x-bind:aria-label="section.label" class="not-last:mb-1.5">
                                    <div x-text="section.label" x-bind:class="page() ? 'sr-only' : ''" class="px-2.5 pt-2 pb-1 text-caption font-medium text-muted-foreground"></div>
                                    <template x-for="row in section.items" x-bind:key="row.c.id">
                                        <div role="option" data-slot="command-item" x-bind:id="optionId(row.i)" x-bind:data-id="row.c.id" x-bind:aria-selected="row.i === highlighted ? 'true' : 'false'"
                                            x-bind:aria-disabled="row.c.disabled ? 'true' : null" x-bind:data-highlighted="row.i === highlighted ? '' : null" x-bind:data-disabled="row.c.disabled ? '' : null"
                                            x-on:mousemove="highlighted = row.i" x-on:click="run(row.c)"
                                            class="group flex h-10 min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-3 rounded-control px-2.5 text-body-sm text-foreground outline-none [scroll-margin-block:0.375rem] data-highlighted:bg-nq-selected data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground data-highlighted:[&_svg]:text-foreground">
                                            <span x-show="row.c.iconHtml" x-html="row.c.iconHtml" class="contents"></span>
                                            <span x-text="row.c.label" class="min-w-0 flex-1 truncate"></span>
                                            <span x-show="row.c.hint" x-text="row.c.hint" class="shrink-0 text-caption text-muted-foreground"></span>
                                            <span x-show="row.c.shortcut" class="flex shrink-0 gap-0.5" dir="ltr">
                                                <template x-for="k in (row.c.shortcut ? keys(row.c.shortcut) : [])"><kbd data-slot="kbd" dir="ltr" x-text="k" class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground"></kbd></template>
                                            </span>
                                            <svg x-show="row.c.children" aria-hidden="true" class="size-3.5! rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m9 18 6-6-6-6"/>
</svg>                                        </div>
                                    </template>
                                </div>
                            </template>
                        </div>
                    </div>
                    <div class="flex items-center gap-4 border-t border-border bg-nq-surface-soft px-4 py-2 text-caption text-muted-foreground">
                        <span class="flex items-center gap-1.5">
                            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">↑</kbd>
                            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">↓</kbd>
                            Navigate
                        </span>
                        <span class="flex items-center gap-1.5">
                            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 4v7a4 4 0 0 1-4 4H4"/>
  <path d="m9 10-5 5 5 5"/>
</svg></kbd>
                            Open
                        </span>
                        <span x-show="pages.length" class="flex items-center gap-1.5">
                            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">⌫</kbd>
                            Back
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </template>
</div>
</div>
```
