Skip to content

Chat Cards

Chat Cards

Interactive card deck in a GHCP canvas: tab cards, tables, SVG charts, show/hide sections, numbered outlines, rendered markdown documents, video clips, and forms whose submissions come back to the conversation as prompts.

Documentation

Chat Cards

Interactive card deck for GitHub Copilot canvas. Instead of walls of text, the agent explains things visually with a live deck of interactive cards: tab boxes, tables, SVG charts, forms, collapsible sections, numbered outlines, rendered markdown documents, and short video clips. Forms are two-way: submitting one sends the values back to the conversation as the agent's next prompt, so a card can gather context and steer the session as easily as it presents information.

The extension is a port of an MCP server, which is similar; but renders the cards inline of the conversation, instead of rendering the HTML in a separate panel.

Chat Cards canvas preview

What is in the deck

Action Card
create_tab_card Tabbed views of one subject (markdown, text, HTML, or code per tab with copy buttons)
create_table_card HTML table from explicit rows or loosely delimited raw text (delimiter auto-detected, URL cells become links)
create_chart_card SVG bar, line, pie, or donut chart with legend and a collapsible data table
create_form_card Form whose submission becomes the next conversation prompt
create_reveal_card Collapsible show/hide sections with show-all/hide-all controls
create_list_card Nested sequential outline numbered 1., 1.1., 1.1.1.
create_markdown_card Render a markdown document as one card: H1 becomes the title, H2 sections fold into reveals
create_video_card HTML video player for a short clip (direct file URL, data:video/*, or blob:)
update_card Re-render an existing card in place
remove_card / clear_cards Take cards off the deck
list_cards See the deck (the user may have reordered or removed cards)
get_form_responses Read form submissions, newest first

Every card keeps the signature interactions of the MCP version: a "Copy card" button that copies the card as a standalone HTML document, a </> toggle that shows the card's own HTML source, a collapse toggle, drag to reorder cards in the deck, tutor terms that show tooltips after a hover dwell, and model-defined right-click actions that send prompts back to the conversation ({{selection}} in an action prompt is replaced with the user's selected text). The deck has a light and a dark theme.

Example action inputs

create_table_card:

{
  "title": "JavaScript array methods",
  "headers": ["Method", "Purpose"],
  "rows": [
    ["map", "Transform each item"],
    ["filter", "Keep matching items"],
    ["reduce", "Fold items into one value"]
  ]
}

create_form_card:

{
  "title": "Study preferences",
  "promptTemplate": "Teach {{topic}} with {{style}} examples.",
  "fields": [
    { "name": "topic", "label": "Topic", "required": true },
    { "name": "style", "type": "select", "options": ["practical", "theoretical"] }
  ]
}

When the user submits the form, the filled template is handed to the agent as the next prompt. If the handoff fails, the card reveals the prompt text with a copy button, and the agent can always read submissions later with get_form_responses.

Installation

Requires Node.js 18.17 or newer and a GitHub Copilot client that supports canvas extensions (such as GitHub Copilot CLI).

Drop this folder at ~/.copilot/extensions/chat-cards/ for user scope, or in a repository at .github/extensions/chat-cards/ for project scope. Then install dependencies from inside the copied folder:

# User scope
cd ~/.copilot/extensions/chat-cards

# Or project scope, from the repository root
cd .github/extensions/chat-cards

npm install

npm install pulls the extension's single dependency (@github/copilot-sdk). Then register the extension/ folder with your Copilot client as a local extension, start a session, and ask the agent to open the Chat Cards canvas.

Reload extensions in the GitHub Copilot app, then open the chat-cards canvas in a conversation.

How the port maps to the MCP server

MCP server Canvas extension
MCP tools (create_tab_card, ...) Canvas actions with the same names and input shapes
MCP Apps iframe / embedded HTML resource Live canvas page served on 127.0.0.1
Form submit posts ui/message to the host Form submit calls session.send({ prompt })
Card HTML per tool result Deck state pushed to the page over SSE
src/cards/* + src/util/* (TypeScript) cards-core.mjs (dependency-free port, testable without the SDK)

Deliberate differences:

  • Stricter HTML sanitizing. Tab and reveal content can be markdown, text, code, or HTML, like the MCP server, but where the server sanitizes HTML with a parser dependency, the extension rebuilds it against a strict tag and attribute allowlist: unknown or malformed tags render as visible literal text, attributes are re-emitted from scratch, and closing tags are balanced. A tab or section with no usable content is rejected with an error naming it, instead of rendering an empty panel.
  • No file, archive, or web-fetch actions. read_local_file, unpack_archive, fetch_reference, mirror_web_form, and submit_web_form stay MCP-only: a Copilot agent already reads files and pages natively, so it passes content inline (for example create_markdown_card takes the markdown itself rather than a path).
  • No multi-part splitting. MCP hosts cap tool-result sizes, so the server splits large documents into parts. The canvas renders directly and needs no parts; the deck instead keeps at most 60 cards, dropping the oldest.

Folder layout

extension/
  extension.mjs           Canvas/session wiring, actions, local HTTP + SSE server
  cards-core.mjs          Card builders and rendering (no SDK import; unit-testable)
  copilot-extension.json  Plugin manifest for the awesome-copilot submission
  package.json            Extension package manifest
  assets/
    canvas.html           The canvas page (theme, card runtime, deck UI)
    preview.png           Screenshot used as the extension logo/preview

Security notes

  • The card server binds to 127.0.0.1 on an ephemeral port; every request must carry the per-canvas token, and request bodies are size-capped.
  • All model- and user-supplied text is HTML-escaped before it reaches card markup, and the markdown renderer emits only escaped text nodes.
  • The canvas page loads no external scripts, stylesheets, or fonts.
  • Prompts sent from the canvas (form submissions and context actions) are length-capped.

Development

The rendering core has no SDK dependency, so it can be exercised directly:

node -e "import('./cards-core.mjs').then(m => console.log(m.buildTableCard({ title: 'Demo', rows: [['a', 'b']] }).summary))"

To run the extension itself, follow Installation above.