Artifact panel
The side panel for artifacts: versions, Code and Preview, copy, download and full screen. Resizable on desktop, a bottom sheet on phones.
On this page
Installation
Usage
import { ArtifactLayout, ArtifactPanel } from "@/components/ui/chatcn/artifact-panel"
import { JsxPreview } from "@/components/ui/chatcn/jsx-preview"
<ArtifactLayout
open={open}
onOpenChange={setOpen}
panel={
<ArtifactPanel
title="Usage meter"
label="React component"
language="tsx"
content={code}
streaming={streaming}
versions={versions}
version={versionId}
onVersionChange={setVersionId}
onClose={() => setOpen(false)}
preview={({ content, streaming }) => <JsxPreview jsx={content} streaming={streaming} />}
/>
}
>
<Conversation />
</ArtifactLayout>ArtifactLayout puts the panel beside the conversation at 45% by default, with a 360 px minimum, on top of Resizable. Below 768 px it becomes a bottom sheet. ArtifactPanel also works on its own inside any layout.
While streaming is true the panel shows code, follows the end and disables Download. When writing finishes it switches to the preview by itself, unless the reader picked a view.
The code view is a plain monochrome view with line numbers. To highlight it, pass renderCode={({ content, language, streaming }) => <CodeBlock ... />}.
Examples
HTML preview
HTML artifacts preview in an iframe with sandbox="allow-scripts allow-forms allow-popups allow-modals": scripts run, but on an opaque origin with no access to your app. While the HTML streams, the frame keeps the last complete version instead of reloading on every token.
Mobile sheet
On small screens the panel slides up as a sheet over 500 ms on the drawer curve. Drag the grabber down to dismiss; a quick flick works too.
Props
ArtifactPanel
| Prop | Type | Notes |
|---|---|---|
title, label | ReactNode | Header text |
kind | "code" | "document" | "html" | "image" | HTML previews content in a sandbox, images show content as a URL |
content | string | The source |
language, filename | string | Download name and extension |
streaming | boolean | Caret, follow mode, Writing status |
versions, version, onVersionChange | See Artifact versions | |
view, defaultView, onViewChange | "code" | "preview" | Controlled or automatic |
preview | ReactNode | (ctx) => ReactNode | Custom preview, for example JsxPreview |
previewHtml, previewSrc, sandbox | string | iframe preview |
previewError, onFixError | Runtime error bar with Fix error | |
renderCode | (ctx) => ReactNode | Replace the code view |
actions | ReactNode | Extra header actions, use ArtifactPanelAction |
onClose | () => void | Shows Close |
fullscreen, defaultFullscreen, onFullscreenChange | boolean |
ArtifactLayout
| Prop | Type | Notes |
|---|---|---|
open, onOpenChange | Panel visibility | |
panel | ReactNode | Usually an ArtifactPanel |
layout | "auto" | "side" | "sheet" | auto switches to the sheet below 768 px |
defaultSize, minSize, maxSize | number | "Npx" | 45%, 360 px and 75% by default |
minContentSize | number | "Npx" | Smallest conversation width, 320 px |
autoSaveId | string | Persist the split |
Keyboard
| Key | Action |
|---|---|
← → in the view toggle | Switch between Code and Preview, without animation |
Esc in the panel | Leave full screen first, then close the panel |
Esc in the sheet | Close the sheet; focus returns to the card |
| Arrows on the divider | Resize the panel |
Accessibility
- The panel is a labelled
section. Code and Preview are tabs controlling the body. - Opening by itself during streaming never moves focus. The sheet is a modal dialog that traps focus and restores it.
- The code is a focusable scroll region, so keyboard users can scroll long files.
- Writing status sits in a polite live region next to the toggle.
Motion
- Enter: 16 px from the side plus opacity, 280 ms on the default curve. Exit: 200 ms. Content inside never staggers.
- The toggle pill slides 200 ms on the in-out curve with the pointer, and jumps when switched by keyboard.
- The mobile sheet uses 500 ms on the drawer curve and tracks the finger 1:1 while dragged.
- Reduced motion keeps the fades and drops every translate.