Xzero UI
Pro library

AI search barPro

A search card for an app or docs, where typing lists grouped results right away (Docs, Customers, Issues) and pressing Tab streams a short AI answer on top. The answer's numbered citations point to the results below it.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/ai-search-bar
Live · keyboard ready

Pass search(query, signal) so it returns AiSearchResult[] (each with id, group, title, description, meta, icon or issue status), either directly or as a promise. Pass answer({ query, results, signal }) as an async iterable: first yield { type: "sources", ids }, then { type: "text", text } pieces, where [n] cites ids[n - 1] and backticks render as inline code. To show the error state with Try again, throw. Wire onSelect to open a result and onAsk for analytics. Set groups, recent, suggestions and model to match your product. If you pass neither search nor answer, the card plays a scripted demo until someone touches it.

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/ai-search-bar

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { AiSearchBar, type AiSearchAnswerEvent } from "@/components/xzero/blocks/ai-search-bar/ai-search-bar";

async function search(query: string, signal: AbortSignal) {
  const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal });
  return response.json(); // [{ id, group: "docs", title, description, meta }]
}

async function* answer({ query, results, signal }) {
  const response = await fetch("/api/ask", { method: "POST", body: JSON.stringify({ query, ids: results.map(r => r.id) }), signal });
  if (!response.ok) throw new Error("The assistant is busy. Try again in a moment.");
  for await (const event of readNdjson<AiSearchAnswerEvent>(response.body!)) yield event;
}

export function AppSearch() {
  return (
    <AiSearchBar
      search={search}
      answer={answer}
      recent={recentSearches}
      suggestions={["How do I set up SAML SSO?", "How do I rotate an API key safely?"]}
      onSelect={result => router.push(result.href!)}
      onAsk={query => track("search_ask_ai", { query })}
    />
  );
}

API reference

AiSearchBar

One search card for an app or docs. Typing lists grouped results at once (Docs, Customers, Issues from Linear) with the matched words highlighted, and the arrow keys move through them. The first row is "Ask AI about ...": press Tab or click it and a short answer streams in its place, with numbered citations that match small number badges on the results below; hovering or focusing a citation lights its result, and clicking it jumps there. With an empty field it shows recent searches and suggested questions. The field, a fixed height body and a footer with key hints and the model never change size between states. With no props it plays a short scripted search and answer until touched.

PropTypeDefaultDescription
search(query: string, signal: AbortSignal) => AiSearchResult[] | Promise<AiSearchResult[]>–Returns results for a query. Sync results show in the same frame; a promise keeps the previous list and swaps the search icon for a spinner until it resolves. Stop when signal aborts. Each result is { id, group, title, description?, meta?, icon?, status?, href? }. Defaults to a sample search over a developer platform's docs, customers and issues.
answer(request: AiSearchAnswerRequest) => AsyncIterable<AiSearchAnswerEvent>–Streams the AI answer. The request has { query, results, signal } with the results on screen. Yield { type: "sources", ids } first, then { type: "text", text } pieces; [n] in the text cites ids[n - 1] and code spans render as inline code. Throw to show the error state with Try again. Defaults to a scripted demo that sends nothing.
groupsAiSearchGroup[]Docs, Customers, IssuesSections in order: { id, label, icon?, source? }. icon is the default 16px icon for results without one; source ({ name, mark? }) shows where a group comes from on the right of its heading. Results with an unknown group go under Other.
recentstring[]["webhook retries", "Figma", "AUTH-912"]Recent searches shown when the field is empty. Picking one fills the field. New searches and opened results are added for the session, three at most.
suggestionsstring[]–Suggested questions shown when the field is empty. Picking one asks AI right away. The demo offers three.
model{ name: string; mark?: ReactNode }Claude Sonnet 5.5The model named in the footer and on the answer, with its mark.
placeholderstring"Search docs, customers and issues"Placeholder in the empty field.
autoPlaybooleantrue without search or answerPlays a scripted search, Tab and answer, then a suggested question, while on screen. Any pointer, key or focus inside the card stops it for good. Plays once with reduced motion.
onSelect(result: AiSearchResult) => void–Called when a result is opened with Enter or a click. Without it the footer confirms the open and the status says it was simulated.
onAsk(query: string) => void–Called when AI is asked, just before the answer streams.
classNamestring–Class on the card. It is 100% wide up to 640px.

Keyboard interactions

KeysAction
Arrow down / Arrow upMoves through Ask AI and the results, or through recent searches and suggestions when the field is empty. Wraps at both ends and keeps the active row in view.
EnterOpens the active result, fills the field with a recent search, asks a suggested question, or asks AI from the Ask AI row.
TabWith text in the field and no answer yet, asks AI about it. Otherwise moves focus as usual: clear, copy and close on the answer, then each citation.
EscapeCloses the answer first, then clears the field.
Enter, SpaceOn a citation, jumps to its result and makes it active.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Guidance for AI tools

Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components