Skip to content
Sign in
Chat layoutsCanvas chatMenu

Canvas chatPro

A chat on an infinite canvas where prompts and answers are nodes you can branch from, drag, pan and zoom.

ProA latency question on a canvas: the first answer branches twice, each branch growing out of it on its own edge.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/canvas-chat

Usage

tsx
import { CanvasChat } from "@/components/ui/chatcn/pro/canvas-chat"

<CanvasChat
  nodes={nodes}
  onSend={({ text, parentId }) => ask(text, parentId)}
  onNodeMove={(id, position) => save(id, position)}
  status={status}
  onStop={stop}
/>

The canvas lays the conversation out as a tidy tree and draws each reply on a curved edge from the answer it grew from. Select any answer, or press Branch from here, and the composer says where the next prompt goes; send it and the new prompt slides out of its parent along a fresh edge. The open path is drawn in ink. Drag a node to place it (edges follow every frame) and store the position with onNodeMove; pinned positions come back through x and y. Pan by dragging the background, holding Space or scrolling; zoom to the cursor with Ctrl or a pinch. The first view starts at the top at a readable size; Fit shows the whole tree. A failed answer, when selected, sends the next prompt as another take on its question.

Examples

States

ProAn empty canvas to start from, a failed branch beside one still thinking, and a read only canvas without the composer.

Props

PropTypeNotes
nodes{ id, parentId, role, text, status?, error?, label? }[]A flat list; each node points at its parent. parentId: null starts a tree
x, y on a nodenumberPins it; otherwise it is laid out
selectedId, defaultSelectedId, onSelectedChangestring | nullThe node new prompts branch from; defaults to the newest answer
onSend({ text, parentId }) => voidFrom the built in composer
onNodeMove(id, { x, y }) => voidWhen a drag or a nudge ends
status, onStop"idle" | "submitting" | "streaming"Send becomes Stop
minimapbooleanDefault on, hidden below 560 px
composerbooleanOff for a read only canvas
clampHeightnumberAnswers taller than this fold behind Show all. Default 240
renderAnswer(node) => ReactNodeDefaults to Markdown
disabledbooleanNo dragging or sending
labelsPartial<…Labels>Every string, for translation

Keyboard

KeyAction
Up and DownParent, and the child on the open path
Left and RightPrevious and next branch
HomeThe first prompt
EnterBranch from the focused node
Shift and an arrowNudge the focused node 24 px
+, - and 0Zoom in, out and back to 100%
Shift 1Fit the tree to the view
Space and dragPan

Accessibility

  • Nodes are a single tab stop with arrow keys between them; each is named by its role, an excerpt and its branch count.
  • Focus pans the camera just enough to bring the node into view.
  • The composer always says which node it branches from, in text.
  • Zoom and fit controls are a toolbar with labelled buttons and their shortcuts in tooltips.

Motion

A new branch starts at its parent and eases into place, about 300 ms with a critically damped follow, while its edge is redrawn every frame so the curve stretches behind it. Dragging, panning and pinching track the pointer 1:1. Zoom and fit from the buttons glide in 280 ms and jump from the keyboard. Reduced motion places everything at once.