Skip to content
Sign in
ConversationExport menuMenu

Export menu

Export or share a conversation: copy as Markdown, download Markdown or JSON, print or save as PDF, and copy a share link with an async success state. Comes with chatcn-export, the helpers that turn ChatcnMessage[] into Markdown, JSON and print HTML.

From the chat header: copy or download Markdown, JSON, print, or copy a share link.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/export-menu.json

Usage

tsx
import { ExportMenu } from "@/components/ui/chatcn/export-menu"

<ExportMenu
  messages={messages}
  title={chat.title}
  onCreateLink={async () => (await createShareLink(chat.id)).url}
/>

Copy actions confirm inside the menu (Copied, Link copied) and close 900 ms later. Creating a link shows a spinner in the item until the promise resolves; a failure stays open and says Link failed, try again. Downloads and print close at once, because the browser takes over. Print renders only the conversation, from a hidden frame, so the app around it never ends up in the PDF.

The helpers

ts
import { toJSON, toMarkdown, toPrintHtml, downloadText, exportFilename } from "@/lib/chatcn-export"

const md = toMarkdown(messages, { title, includeReasoning: false, tools: "summary" })
downloadText(exportFilename(title, "md"), md, "text/markdown")

Markdown puts each author in bold, separates turns with a rule, and turns each tool call into one line such as Used edit_file on src/queue/webhooks.ts, done. Reasoning is left out unless asked. JSON is lossless, so the file can be loaded back as messages. Unknown parts are skipped, never fatal.

Examples

Export menu

Copy, two downloads, print, a share link and the reasoning toggle.

Markdown output

toMarkdown on its own: tool calls as one-line summaries, reasoning only when asked.

States

A share link that fails, an empty conversation and an icon trigger with downloads only.

Props

PropTypeNotes
messagesChatcnMessage[] | () => ChatcnMessage[]Required. A function runs only on export
titlestringHeading, print title and file names
formatsExportFormat[]Which actions, in order. Defaults to all five
linkstringA ready share link
onCreateLink() => Promise<string> | stringCreates a link on demand
includeReasoning, defaultIncludeReasoning, onIncludeReasoningChangebooleanControlled or uncontrolled
reasoningTogglebooleanShow Include reasoning. Defaults to on when any reasoning exists
optionsChatcnExportOptionstools, includeSystem, roleLabels
onPrint(html: string) => voidReplace the default print
onExport(format, content) => voidAfter every export, with what was produced
triggerReactElementYour own trigger, such as an icon button
align, sideMenu placement. Defaults to end and bottom
disabledbooleanAlso disabled when messages is empty

Keyboard

KeyAction
Enter, Space, ArrowDownOpen the menu from the trigger
ArrowDown, ArrowUpMove between actions. Highlight is instant
EnterRun the action
EscClose and return focus to the trigger

Accessibility

  • A Base UI menu: roving focus, typeahead and focus return. Copy results and failures are announced through a polite live region.
  • Success is green with a check and a new label, failure red with an alert icon and a new label, so color is never the only signal.
  • Items are 44 px tall on touch screens.

Motion

  • The menu opens from its trigger in 180 ms and closes in 140 ms. Opened from the keyboard, it skips the animation.
  • Item icons swap to a spinner, check or alert with the 150 ms blur crossfade, instantly when chosen from the keyboard.