Kanban Board

Work items from the user's tracker by stage. The user reorganizes them, and every change goes back to the agent instead of being written directly.

Pattern

≈ 2,158 tokens per use (tool 893 · guidance 303 · typical payload 962)How token costs are estimated

Example: Kanban Board

Show me the sprint board

Sprint 42

14 cards

Acme Tracker · Web team (opens in a new tab)

Press Space or Enter to pick up this card. Then use the arrow keys to move it between and within columns, Space or Enter to drop it, or Escape to cancel. You can also use the card's move menu. Nothing is saved until you apply.

Backlog

2 cards
  • Labels: UI
    Low priorityENG-151
  • Labels: Security
    Medium priorityENG-155

To do

3 cards

In progress

4 cards, limit 3Over limit

Review

2 cards, limit 3

Done

3 cards

When to use

  • The user wants to see work items by stage: a sprint board, a bug triage, a hiring or sales pipeline.
  • The user wants to reorganize items between stages, or the agent is suggesting moves for them to review.

When not to use

  • There are only a few items or a single stage. Use a list.
  • The user needs to compare many fields per item. Use a Data Table.
  • The agent can't read the items from the tracker. Fetch them first; never invent cards.
  • The change is to a field other than the stage. Use a Change Preview.

How the agent should use it

  • Pass columns and cards exactly as the tracker returns them, in order. A card belongs to the column named by its columnId.
  • Put recommended moves in proposals, each with a one-line reason. They show already applied and marked "Suggested · from Inbox", with Undo; the reason is read to screen readers, not shown on the card. Never pre-move a card by changing its columnId.
  • Set editable: false when the user only asked to look. Without proposals, the board is display-only.
  • Apply nothing until the result arrives, then apply exactly the returned moves.

Moving cards

Nothing is written while the user rearranges. Every move is collected and shown on the card ("Moved from In progress", with Undo); the footer counts them ("3 changes"), and the agent receives the moves only when the user applies. Reset restores the starting arrangement and Request changes (the message icon) opens a feedback box; with proposals, Reset becomes an icon button next to Dismiss.

InputHow
MouseDrag a card. A line shows where it will land; the board scrolls when you drag near its edge
Touch and penPress and hold a card, then drag. A quick swipe still scrolls the board
KeyboardFocus a card's title, press Space or Enter to pick it up, use ←/→ to change column and ↑/↓ to change position, then Space or Enter to drop. Escape cancels. Each step is announced to screen readers
Move menuEach card's move button lists the columns, for anyone who prefers not to drag. It shows when you hover or focus a card (always on touch screens)

Each column is a filled panel with its own header block (the title, then the card count beside it, and "Over limit" at the right); cards sit in the panel on a lighter fill. Each card shows its title over one quiet row: priority (an Urgent red or High orange badge; medium and low are a small muted signal), subtitle, due date and the assignee's initials. Columns with a limit show "3 / 4" and say "Over limit" when they hold more cards. Due dates show "Overdue" once they're past; pass now to fix the date that's compared against (it defaults to the viewer's clock). A column marked done shows its cards faded with a filled check. When the columns are wider than the card, previous and next arrows appear beside the title (with edge fades on the board) and scroll one column at a time.

Installation

npx shadcn@latest add https://your-site.com/r/kanban-board.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { kanbanBoardTool } from "@/components/genui/kanban-board.schema"

const tools = {
  // No execute(): the call is completed by the user's decision in the UI.
  [kanbanBoardTool.name]: tool({
    description: kanbanBoardTool.description,
    inputSchema: kanbanBoardTool.inputSchema,
  }),
}
components/message.tsx
<KanbanBoard
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : undefined}
  result={part.state === "output-available" ? part.output : undefined}
  onResult={(result) =>
    addToolResult({ tool: "show_board", toolCallId: part.toolCallId, output: result })
  }
/>

The agent receives one of:

{ action: "apply", moves: [{ cardId: "eng-142", fromColumnId: "doing", toColumnId: "done", index: 0 }] }
{ action: "revise", feedback: "Keep the payment bugs in Now" }
{ action: "dismiss" }

moves is the shortest list that turns the original board into the user's: accepted suggestions are included, and a card moved away and back again is not. Each index is the card's final 0-based position in its target column. On revise or dismiss, apply nothing.

A read-only board (editable: false, no proposals) sends nothing back, so give that tool call an execute that returns immediately.

With LangGraph

Push it from a graph node with ui.push({ name: "show_board", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. The board restores the final arrangement read-only, with "Applied" (or the requested changes, or "Dismissed") in the footer, and never calls onResult again.

<KanbanBoard {...part.input} result={part.state === "output-available" ? part.output : undefined} />

Schema

PropTypeNotes
titlestring?e.g. "Sprint 42"
source{ label, url? }Data-bound. Where the board comes from
columns{ id, title, limit?, done? }[]Data-bound. 1–8 stages, in order. limit is the WIP limit; done marks a finished stage
cardsKanbanCard[]Data-bound. In order within each column, top first
proposals{ cardId, toColumnId, reason? }[]Moves the agent suggests, shown pre-applied as "Suggested"
editablebooleanLet the user move cards. Default true
submitLabelstring?Apply button label. Default "Apply N changes"

KanbanCard

FieldTypeNotes
idstringReturned in moves
columnIdstringThe column it's in now
titlestring
subtitlestring?A short key, e.g. "ENG-142"
assignee{ name, initials }?Shown as initials
labelsstring[]?Up to 3. Read to screen readers, not shown on the card
duestring?ISO date, e.g. "2026-10-14"
priority"urgent" | "high" | "medium" | "low"Urgent and high are shown as badges, medium and low as a small signal; all have a text label for screen readers
urlstring?http(s) link to the item. The card's subtitle (its key) becomes the link and opens in a new tab, with no icon; give the card a subtitle

Host props: status (streaming, ready, submitting, applied, revised, dismissed), onResult, result, now and className.

Source

On this page