AI command
ProA Cmd+K command palette with an AI mode. Search pages and actions, or press Tab to ask a question: the answer streams inside the palette with numbered sources, and the next steps it suggests, like filing a Linear issue or opening a Notion doc, run straight from the list.
- A global Cmd+K in a SaaS app where people both jump to pages and ask questions about their workspace.
- Search that should fall back to an AI answer when nothing matches.
- Answers that lead straight to an action in another tool, like filing a Linear issue or posting to Slack.
- A long, multi-turn conversation: use a chat thread.
- Plain navigation with no AI: use command-palette.
- Inline field suggestions while typing in a form: use an autocomplete input.
Installation
Pro source and install commands unlock with a Pro plan.
import { AiCommand } from "@/components/arc/ai-command/ai-command"; // Sample walkthrough<AiCommand /> // Wired to your app and model<AiCommand items={commands} onSelect={item => { router.push(item.id); setOpen(false); }} onAsk={async (question, { signal, append, setSources }) => { const res = await fetch("/api/ask", { method: "POST", body: JSON.stringify({ question }), signal }); const { sources, stream } = await readAnswer(res); setSources(sources); for await (const delta of stream) append(delta); return { actions: [{ id: "issue", label: "Create issue in Linear", run: s => createIssue(question, s) }] }; }} onClose={() => setOpen(false)} elevated/>API reference
3 parts. The first is the root.
AiCommand
A fixed-size command palette with an AI mode. Search pages and actions, or press Tab to ask: the answer streams in place with numbered sources and runnable next steps. With no props it plays a sample walkthrough until someone interacts.
itemsAiCommandItem[]sample workspacePages and actions to search: { id, label, group?, icon?, meta?, shortcut?, keywords?, suggested?, onSelect? }. Rows marked suggested show while the query is empty.onSelect(item: AiCommandItem) => void–Runs when a page or action is chosen with Enter or a click. Close your dialog or navigate here.onAsk(question, { signal, append, setSources }) => Promise<AiCommandAnswer | void>simulated engineAnswers a question. Stream text with append, show sources early with setSources, and resolve with { text?, sources?, actions? }. Throw to show the error state with Retry. The signal aborts when the person asks again or goes back.onAction(action: AiCommandAction, result: string) => void–Runs after a suggested action finishes, with the label it ended on (for example "Created ENG-2481").onSourceSelect(source: AiCommandSource) => void–Runs when a source chip or citation is chosen. Sources with an href open in a new tab instead.suggestedQuestionsstring[]3 sample questionsQuestions offered while Ask AI is open and empty.model{ name: string; mark?: ReactNode }{ name: "Claude Sonnet 5.5" }The model that answers, shown in the footer, the input and beside each answer.defaultMode"search" | "ask""search"Mode on first render.defaultQuerystring""Query on first render.onModeChange(mode: "search" | "ask") => void–Runs when the palette switches between Search and Ask AI.onClose() => void–Escape on an empty search. Use it to close the surrounding dialog.placeholder{ search?: string; ask?: string }–Input placeholders per mode.thinkingLabelstring"Searching your workspace"Status while the model works, before any text arrives.autoPlaybooleantrue without items and onAskPlays a looping walkthrough while on screen. Stops for good on the first real pointer or key press inside, and never runs with reduced motion.hotkeybooleanfalseListens for Cmd+K or Ctrl+K on the page and focuses the input.heightnumber392Height of the results area in px. The palette keeps this size while typing, switching modes and streaming.elevatedbooleanfalseAdds the floating shadow, for use inside a dialog or popover.classNamestring–Class on the root.styleCSSProperties–Inline styles on the root.aria-labelstring"Command palette"Accessible name of the palette.AiCommandAction
A suggested next step returned with an answer and run in place.
idRequiredstring–Stable id.labelRequiredstring–Row label, such as "Create issue in Linear".metastring–Quiet detail after the label.markReactNode–16px brand mark or icon.run(signal: AbortSignal) => Promise<string | void>–Does the work. Resolve with a done label or nothing; throw to show the error state (Enter retries).runningLabel / doneLabel / errorLabel / doneMetastring–Labels for each state. doneMeta is where a simulated action says nothing was sent.AiCommandSource
A document the answer cites as [1], [2] in its text.
idRequiredstring–Stable id.titleRequiredstring–Short chip title, such as "billing-api #1893".descriptionstring–Longer text for the tooltip and screen readers.markReactNode–14px brand mark or icon.hrefstring–Opens in a new tab when chosen.- UporDown
- Move through results, questions or suggested actions.
- Enter
- Open the page, run the action, ask the question, or retry a failed answer or action.
- Tab
- Ask AI about the current query. With an empty query it opens Ask AI with suggested questions; in that state Tab goes back to search.
- Escape
- From Ask AI, back to search (cancels a streaming answer). In search, clears the query, then calls onClose.
- CmdorCtrl+K
- Focuses the input when hotkey is on.
- The input is a combobox that owns a listbox; the active row is announced through aria-activedescendant, so focus never leaves the input.
- A polite live region announces questions, finished answers, action results and simulated runs ("Simulated, nothing was sent").
- Status is never color alone: running, done and failed actions change both the icon and the label.
- Citations and source chips are buttons with full descriptions as their accessible names; hovering or focusing one highlights its partner.
- The mode toggle uses aria-pressed buttons and keeps input focus.
- The palette never changes size: the results area has a fixed height and views crossfade in place (100ms out, 160ms in, 2px rise).
- One highlight glides between rows in 130ms and jumps without travel when the list itself changes, so typing never animates.
- Answers stream 3 to 7 words every 34ms, each chunk fading in over 140ms; the view follows the text down unless you scroll up.
- Labels, status icons and footer hints swap with a single crossfade. The done check draws once.
- Reduced motion: no walkthrough, no chunk fades or travel; the answer reveals in large steps and every state stays.
- The walkthrough pauses off screen through IntersectionObserver and restarts from a clean state.
- Fills its container up to 680px and holds up at 360px; long labels and meta truncate with an ellipsis, meta first.
- Below 540px the navigate hint hides from the footer; source chips wrap.
- Rows are 40px tall and work with touch; hover only moves the highlight on fine pointers that actually move.
- Search is a synchronous word match over the items array, fine for a few hundred rows; filter on the server beyond that.
- Streaming appends one span per chunk; the highlight is positioned directly in the DOM without extra renders.
- The walkthrough and its timers stop off screen and on unmount; in-flight answers and actions abort through their signals.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use for a product-wide Cmd+K that can also answer questions; put it inside a dialog with elevated and onClose.
- Return sources before text with setSources so the chips land first, and cite them in the text as [1], [2].
- Give each suggested action a run that takes the AbortSignal; return the created object's name ("Created ENG-2481") as the done label.
- Keep item labels short and put context in meta; use group to section results.
- For a plain command menu without AI, use command-palette instead.
The full library index for assistants is at /llms.txt.