Xzero UI
Pro library

AI side panelPro

An assistant docked beside a page that reads it and edits it: ask about the page or a selected passage, review each edit as a word diff, and apply it with a tint sweep on the page and one-click undo.

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

Pass the page as blocks (headings, labelled paragraphs, tables) and respond({ prompt, page, selection, signal }), which returns { text, change? }; text can be a string or an async iterable of deltas, and change ({ blockId, after }) shows as a diff to approve. Persist edits in onApply and onUndo. It fills its container; set --side-panel-height to embed it smaller.

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-side-panel

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 { AiSidePanel } from "@/components/xzero/blocks/ai-side-panel/ai-side-panel";

export function SpecPage({ spec }) {
  return (
    <AiSidePanel
      page={spec}
      respond={async ({ prompt, page, selection, signal }) => {
        const res = await fetch("/api/assistant", {
          method: "POST",
          body: JSON.stringify({ prompt, page, selection }),
          signal,
        });
        const { text, change } = await res.json(); // change: { blockId, after: PageBlock }
        return { text, change };
      }}
      onApply={(change, page) => saveSpec(page)}
      onUndo={(change, page) => saveSpec(page)}
    />
  );
}

// Stream real tokens: return text as an async iterable of deltas.
async function* deltas(stream) {
  for await (const event of stream) if (event.type === "text") yield event.text;
}

API reference

AiSidePanel

An assistant docked beside a page that reads the page and edits it. The header shows the assistant, its model mark and context chips (This page, plus a selection you send over). Answers stream in fast chunks; an answer that changes the page brings a compact word diff with Apply and Discard. Apply sweeps a soft accent tint across the changed part of the page while the new text crossfades in, and an Undo pill appears above the composer. Selecting text in the page shows an Ask about selection button that adds it as a context chip. The panel collapses to the side with no bounce while the page reflows, and becomes a bottom sheet in containers narrower than 640px.

PropTypeDefaultDescription
pagePanelPagesample specThe page the assistant reads and edits: { title, meta?, blocks }. Blocks are headings, paragraphs (with a label such as "Intro") and tables (label, columns, rows). The panel keeps its own copy and reports edits through onApply and onUndo; passing a new object resets it.
respond(request: PanelRequest) => PanelReply | Promise<PanelReply>scripted demoAnswers a prompt. The request has { prompt, page, selection?, signal }; abort work when signal fires (Stop). Return { text, change? }: text is a string (streamed at about 95 words a second) or an AsyncIterable<string> of deltas from your model; change is { blockId, after } and shows as a diff to approve. Throw to show the error state with Retry.
onApply(change: AppliedChange, page: PanelPage) => void–Called after Apply with the change (blockId, after and the before block it replaced) and the page after it. Persist the page here.
onUndo(change: AppliedChange, page: PanelPage) => void–Called after Undo with the reverted change and the restored page.
defaultOpenbooleantrueWhether the panel starts open.
suggestionsstring[]["Summarize this page", "Tighten the intro", "Add an annual price column"]Suggestion chips in the empty thread. Unused ones follow each finished answer.
assistant{ name?: string; model?: string; mark?: ReactNode }{ name: "Assistant", model: "Claude Sonnet 5.5", mark: Claude mark }Identity shown in the panel header, the reopen button and status messages.
classNamestring–Class on the root. It fills its container's width and the screen's height; set --side-panel-height to fit a smaller area and --side-panel-page (default 680px) for the page column width.

Keyboard interactions

KeysAction
TabMoves through the page's Ask about selection button, the reopen button, the panel header, suggestions, change actions, the Undo pill and the composer.
EnterSends the message from the composer, or activates the focused chip or button.
Shift + Arrow keysWith caret browsing, extends a text selection in the page; releasing Shift shows Ask about selection.
EscapeCloses the panel from anywhere inside it and returns focus to the reopen button.

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