# `PhiaUi.Components.Command`
[🔗](https://github.com/charlenopires/PhiaUI/blob/v0.1.17/lib/phia_ui/components/overlay/command.ex#L1)

Command palette component powered by the `PhiaCommand` vanilla JavaScript hook.

A command palette is a modal search interface — popularised by tools like VS
Code, Linear, and Notion — that lets users quickly navigate and trigger
actions by typing. It is activated globally via `Ctrl+K` / `Cmd+K` and closed
with `Escape`.

Search filtering is **server-side** via `phx-change` on `command_input/1`,
giving you full access to your LiveView's data and LiveView Streams for
efficient DOM updates.

## Architecture

The component provides two top-level container options:

- `command/1` — inline modal with a simple dark backdrop; good for embedded
  palettes within a specific page section
- `command_dialog/1` — centered modal with `backdrop-blur-sm`; the standard
  choice for a global application-wide command palette

Both use `phx-hook="PhiaCommand"` and the same keyboard behaviour.

## Sub-components

| Function            | Purpose                                                   |
|---------------------|-----------------------------------------------------------|
| `command/1`         | Modal overlay with backdrop (inline variant)              |
| `command_dialog/1`  | Centered modal with blur backdrop (global palette variant)|
| `command_input/1`   | Search input (`role="combobox"`, drives `phx-change`)     |
| `command_list/1`    | Results container (`role="listbox"`)                      |
| `command_empty/1`   | Empty state message shown when results are empty          |
| `command_group/1`   | Labeled category section for related results              |
| `command_item/1`    | Selectable result item (`role="option"`)                  |
| `command_separator/1` | Visual divider between groups                           |
| `command_shortcut/1`| Right-aligned keyboard shortcut badge                     |

## Hook Setup

Copy the hook via `mix phia.add command`, then register it in `app.js`:

    # assets/js/app.js
    import PhiaCommand from "./hooks/command"

    let liveSocket = new LiveSocket("/live", Socket, {
      hooks: { PhiaCommand }
    })

## Full Example — Global Command Palette

A common pattern: render the palette once in your app layout, use a LiveView
assign for search results, and navigate on item selection.

    <%# In root.html.heex or app.html.heex, or in a persistent LiveView %>
    <.command_dialog id="global-cmd" title="Command Palette">
      <.command_input id="global-cmd-input" on_change="cmd_search" placeholder="Search pages and actions..." />
      <.command_list id="global-cmd-list">
        <%= if @cmd_results == [] do %>
          <.command_empty>No results for "{@cmd_query}".</.command_empty>
        <% else %>
          <.command_group :if={@cmd_results[:pages] != []} label="Pages">
            <.command_item
              :for={page <- @cmd_results[:pages]}
              on_click="navigate"
              value={page.path}>
              <.icon name="file" class="mr-2 h-4 w-4" />
              {page.title}
              <.command_shortcut :if={page.shortcut}>{page.shortcut}</.command_shortcut>
            </.command_item>
          </.command_group>
          <.command_separator :if={@cmd_results[:pages] != [] and @cmd_results[:actions] != []} />
          <.command_group :if={@cmd_results[:actions] != []} label="Actions">
            <.command_item
              :for={action <- @cmd_results[:actions]}
              on_click={action.event}
              value={action.value}>
              <.icon name={action.icon} class="mr-2 h-4 w-4" />
              {action.label}
            </.command_item>
          </.command_group>
        <% end %>
      </.command_list>
    </.command_dialog>

    # LiveView
    def handle_event("cmd_search", %{"value" => query}, socket) do
      results = MyApp.Search.command_palette(query)
      {:noreply, assign(socket, cmd_query: query, cmd_results: results)}
    end

    def handle_event("navigate", %{"value" => path}, socket) do
      {:noreply, push_navigate(socket, to: path)}
    end

## Example — Scoped Palette (No Dialog Chrome)

Use `command/1` when you want the palette to be scoped to a section and
triggered by something other than `Ctrl+K`:

    <.button phx-click={JS.show(to: "#local-cmd")}>Open Command</.button>

    <.command id="local-cmd">
      <.command_input id="local-cmd-input" on_change="filter_items" />
      <.command_list id="local-cmd-list">
        <.command_group label="Recent Files">
          <.command_item :for={f <- @filtered_files} on_click="open_file" value={f.id}>
            {f.name}
          </.command_item>
        </.command_group>
      </.command_list>
    </.command>

## Keyboard Navigation

The `PhiaCommand` hook provides full WAI-ARIA keyboard support:
- `Ctrl+K` / `Cmd+K` — opens the palette globally (any element focused)
- `ArrowDown` / `ArrowUp` — moves focus between `command_item/1` elements
- `Enter` — activates the focused item (fires its `phx-click` event)
- `Escape` — closes the palette, clears the input, returns focus
- `Tab` — wraps focus within the list (does not close)

## Server-Side Filtering

Unlike client-side filtering (which is fast but limited to pre-loaded data),
PhiaUI's command palette uses `phx-change` to send every keystroke to the
LiveView. This enables:
- Searching across the full database rather than a pre-loaded list
- Permission-filtered results (only show actions the user can perform)
- LiveView Streams for efficient DOM patching when results change
- Async searches with `Task.async` and `handle_info` for heavy queries

## Accessibility

- The palette uses `role="dialog"` and `aria-modal="true"` — screen readers
  treat it as a modal and restrict virtual cursor navigation to the palette
- `command_input/1` has `role="combobox"` and `aria-autocomplete="list"`
  to declare the search+results relationship
- `command_list/1` has `role="listbox"` and items have `role="option"`
- Selected items are indicated via `aria-selected` and `data-[selected]` CSS
- The `aria-label` on `command_dialog/1` provides a name for the dialog
  announced when it opens

# `command`

Renders an inline command palette modal overlay.

Hidden by default. The `PhiaCommand` hook shows it on `Ctrl+K` / `Cmd+K`
and hides it on `Escape`. The backdrop is a semi-transparent black overlay;
clicking it closes the palette.

Use `command_dialog/1` for the more common centered, blur-backdrop variant.
Use `command/1` when you want a simpler overlay or need custom positioning.

## Attributes

* `id` (`:string`) (required) - Unique ID for the command modal element — the hook mount point.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the root `<div>`.
## Slots

* `inner_block` (required) - `command_input/1`, `command_list/1` and related sub-components.

# `command_dialog`

Renders the standard command palette modal — centered on the viewport with
a blurred backdrop.

This is the recommended component for a global application command palette.
It differs from `command/1` in:
- Vertically centered (not at 20% from top)
- `backdrop-blur-sm` on the backdrop for a modern frosted-glass appearance
- `aria-label` for the dialog name

The hook registers `Ctrl+K` / `Cmd+K` globally — no separate trigger button
is needed (though you can also trigger it programmatically).

## Example

    <.command_dialog id="app-cmd" title="Application commands">
      <.command_input id="app-cmd-input" on_change="search" />
      <.command_list id="app-cmd-list">
        <.command_group label="Navigation">
          <.command_item on_click="goto" value="/dashboard">Dashboard</.command_item>
          <.command_item on_click="goto" value="/settings">Settings</.command_item>
        </.command_group>
      </.command_list>
    </.command_dialog>

## Attributes

* `id` (`:string`) (required) - Unique ID for the command dialog element — the hook mount point.
* `title` (`:string`) - Accessible label used as `aria-label` for the dialog. Screen readers announce this when the dialog opens. Defaults to `"Command Palette"`.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the root `<div>`.
## Slots

* `inner_block` (required) - `command_input/1`, `command_list/1` and related sub-components.

# `command_empty`

Renders an empty state message when no command items match the search query.

Show this when the results list is empty. A good empty state is specific
about what is missing:

    <%= if @cmd_results == [] do %>
      <.command_empty>No results for "{@cmd_query}".</.command_empty>
    <% end %>

Avoid generic messages like "No results" — tell users what they searched for
so they can refine their query.

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the empty state `<div>`.
## Slots

* `inner_block` (required) - Empty state message text.

# `command_group`

Renders a labeled group of command items.

Use groups to organize results into meaningful categories — "Pages",
"Actions", "Recent", "Settings". Groups make large result sets easier to
scan quickly.

    <.command_group label="Pages">
      <.command_item on_click="navigate" value="/dashboard">Dashboard</.command_item>
      <.command_item on_click="navigate" value="/reports">Reports</.command_item>
    </.command_group>
    <.command_separator />
    <.command_group label="Actions">
      <.command_item on_click="create_record" value="new">New Record</.command_item>
    </.command_group>

The group heading is `text-xs font-medium text-muted-foreground` — visible
but not competing with the item content.

## Attributes

* `label` (`:string`) (required) - Group heading label — describes the category of results in this section.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the group `<div>`.
## Slots

* `inner_block` (required) - `command_item/1` sub-components.

# `command_input`

Renders the command palette search input.

Uses `role="combobox"` and `aria-autocomplete="list"` to declare the ARIA
relationship between the input and the results list. The hook manages
`aria-activedescendant` to point at the currently highlighted item.

The `phx-change` sends a LiveView event on every keystroke — use debouncing
in your handler or a `phx-debounce` attribute for expensive searches:

    <.command_input id="cmd-input" on_change="search" phx-debounce="200" />

`autocomplete="off"` and `spellcheck="false"` prevent browser autocomplete
dropdowns and red underlines from cluttering the search UI.

## Attributes

* `id` (`:string`) (required) - Input element ID — used by the hook for `aria-activedescendant` management.
* `placeholder` (`:string`) - Placeholder text shown when the input is empty. Defaults to `"Type a command or search..."`.
* `on_change` (`:string`) (required) - LiveView event name sent via `phx-change` on every keystroke. Your handler
  should update `@results` (or equivalent assign) with filtered items.

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the `<input>`.

# `command_item`

Renders a selectable command palette item.

Uses `role="option"` to pair with the `command_list/1`'s `role="listbox"`.
The hook manages `aria-selected` and the `data-selected` attribute for the
currently highlighted item (driven by `ArrowUp`/`ArrowDown`).

When the user presses `Enter` or clicks the item, `phx-click={@on_click}`
fires with `phx-value-value={@value}`. Your LiveView handler receives:

    def handle_event("navigate", %{"value" => "/dashboard"}, socket) do
      {:noreply, push_navigate(socket, to: "/dashboard")}
    end

Add `command_shortcut/1` as the last child to show a keyboard hint:

    <.command_item on_click="navigate" value="/settings">
      <.icon name="settings" class="mr-2 h-4 w-4" />
      Settings
      <.command_shortcut>⌘,</.command_shortcut>
    </.command_item>

## Attributes

* `on_click` (`:string`) (required) - LiveView event name sent via `phx-click` when the item is selected.
* `value` (`:string`) (required) - Item value sent as `phx-value-value` — use to pass page paths, IDs, or action keys.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the item `<div>`.
## Slots

* `inner_block` (required) - Item content — icon + label + optional `command_shortcut/1`.

# `command_list`

Renders the command results container.

Uses `role="listbox"` to form the ARIA combobox pair with the `command_input/1`
that has `role="combobox"`. Screen readers announce this as the list of
available results when the input is focused.

The `max-h-72 overflow-y-auto` limits the visible height and enables
scrolling for long result lists, keeping the palette compact.

## Attributes

* `id` (`:string`) (required) - Element ID for the results list — the hook links the input's `aria-controls` here.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the list `<div>`.
## Slots

* `inner_block` (required) - `command_empty/1`, `command_group/1`, `command_separator/1` sub-components.

# `command_separator`

Renders a visual divider between command groups.

Use separators to create clear boundaries between unrelated categories in
the results list. Typically placed between `command_group/1` elements:

    <.command_group label="Navigation">...</.command_group>
    <.command_separator />
    <.command_group label="Actions">...</.command_group>

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the separator `<div>`.

# `command_shortcut`

Renders a right-aligned keyboard shortcut badge inside a command item.

This is presentational only — the shortcut hint does not register any
keyboard listener. The shortcut should match an actual global shortcut
registered in your application JavaScript.

    <.command_item on_click="navigate" value="/preferences">
      <.icon name="sliders" class="mr-2 h-4 w-4" />
      Preferences
      <.command_shortcut>⌘,</.command_shortcut>
    </.command_item>

The `ml-auto` class pushes the shortcut to the far right of the flex
container, aligned opposite the item label and icon.

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the `<span>`.
## Slots

* `inner_block` (required) - Shortcut text — e.g. `⌘K`, `Ctrl+P`, `⌘,`. Use platform-specific symbols.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
