Skip to main content

Overview

Panel artifacts are standalone web apps that run inside iframes and communicate with the Artifactuse SDK via postMessage. You can build custom panels using any framework - React, Vue, Svelte, or vanilla JavaScript.

Panels Repository

View source code, development docs, and contribution guidelines

Official Panels

PackageDescriptionTier
@artifactuse/json-panelInteractive JSON tree viewer🆓 Free
@artifactuse/svg-panelSVG preview with pan/zoom🆓 Free
@artifactuse/diff-panelSide-by-side diff comparison🆓 Free
@artifactuse/html-panelHTML + Markdown preview🆓 Free
@artifactuse/react-panelReact/JSX preview🆓 Free
@artifactuse/vue-panelVue SFC preview🆓 Free
@artifactuse/form-panelInteractive forms, wizards🆓 Free
@artifactuse/editor-panelCanvas + Video editor⭐ Pro
@artifactuse/code-panelJS + Python code execution⭐ Pro

How Panels Work

┌─────────────────────────────────────────────────────────┐
│  Your App                                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Artifactuse SDK                                  │  │
│  │                                                   │  │
│  │  ┌─────────────────┐    postMessage    ┌───────┐  │  │
│  │  │ ArtifactusePanel│ ◄──────────────► │ Panel │  │  │
│  │  │                 │                   │ iframe│  │  │
│  │  └─────────────────┘                   └───────┘  │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
  1. SDK detects artifact type in AI response
  2. SDK loads appropriate panel URL in iframe
  3. Panel signals ready via panel:ready message
  4. SDK sends artifact data via load:artifact message
  5. Panel renders content and sends events back

Communication Protocol

Panels communicate with the SDK using the standard postMessage API. All messages use the artifactuse type for identification.

SDK → Panel: Loading Artifacts

When the SDK opens a panel, it sends a load:artifact message containing the full artifact data:
// Message structure sent by SDK
{
  type: 'artifactuse',
  action: 'load:artifact',
  data: {
    id: 'artifact-123',
    messageId: 'msg-456',
    type: 'code',                    // 'code' or 'form'
    language: 'json',                // Panel-specific identifier
    title: 'My Data',
    code: '{"key": "value"}',        // The artifact content
    isInline: false,
    isPreviewable: true,
    isPanelArtifact: true,
    createdAt: '2026-01-19T10:24:25.650Z'
  }
}

Artifact Code Formats

The code field contains different formats depending on the panel type:
PanelLanguage ValuesCode FormatNeeds JSON Parse
json-paneljsonJSON string to display✅ Yes
form-panelformJSON form configuration✅ Yes
diff-paneldiffJSON: {oldCode, newCode, language}✅ Yes
editor-panelcanvas, videoJSON: {width, height, shapes}✅ Yes
svg-panelsvgRaw SVG markup❌ No
html-panelhtml, markdown, mdRaw HTML/Markdown❌ No
react-panelreact, jsxRaw JSX code❌ No
vue-panelvueRaw Vue SFC code❌ No
code-paneljavascript, js, python, pyRaw code❌ No

SDK → Panel: Theme Updates

The SDK sends theme updates when the user changes themes:
{
  type: 'artifactuse',
  action: 'theme:change',
  data: {
    theme: 'dark',        // 'dark' or 'light'
    colors: { ... }       // Optional custom colors
  }
}

Panel → SDK

// Send messages back to the SDK
function sendToSDK(action, data) {
  window.parent.postMessage({
    type: 'artifactuse',
    action,
    data,
    timestamp: Date.now()
  }, '*');
}

// Signal ready when panel loads (required!)
sendToSDK('panel:ready', { timestamp: Date.now() });

// Send form submission
sendToSDK('form:submit', { formId, action: 'submit', values, timestamp: Date.now() });

// Send form cancellation
sendToSDK('form:cancel', { formId, action: 'cancel', timestamp: Date.now() });

// Update artifact data
sendToSDK('artifact:update', { id, data });

// Notify export complete
sendToSDK('export:complete', { filename, size, type });

Building a Custom Panel

1

Create project

Set up a project with your preferred framework (React, Vue, Svelte, vanilla JS)
2

Signal ready

Send panel:ready message when your panel loads
3

Handle load:artifact

Listen for load:artifact message and render the artifact content
4

Handle theme changes

Listen for theme:change message and update your UI
5

Send events

Emit events back to the SDK when user interacts
6

Deploy

Host on your CDN and configure SDK to use it

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>My Custom Panel</title>
  <style>
    body { font-family: system-ui; margin: 0; padding: 20px; }
    .dark { background: #111827; color: #f3f4f6; }
    .light { background: #ffffff; color: #111827; }
  </style>
</head>
<body class="dark">
  <div id="content">Loading...</div>
  
  <script>
    let currentTheme = 'dark';
    let parentOrigin = null;
    
    // Send message to SDK
    function sendToSDK(action, data = {}) {
      window.parent.postMessage({
        type: 'artifactuse',
        action,
        data,
        timestamp: Date.now()
      }, parentOrigin || '*');
    }
    
    // Handle incoming messages
    window.addEventListener('message', (event) => {
      const { type, action, data } = event.data || {};
      if (type !== 'artifactuse') return;
      
      // Store parent origin for responses
      if (!parentOrigin) parentOrigin = event.origin;
      
      switch (action) {
        case 'load:artifact':
          handleArtifact(data);
          break;
        case 'theme:change':
          document.body.className = data.theme || 'dark';
          currentTheme = data.theme;
          break;
      }
    });
    
    // Handle artifact loading
    function handleArtifact(artifact) {
      console.log('Loading artifact:', artifact);
      
      // Check language
      if (artifact.language !== 'my-language') {
        console.warn('Unsupported language:', artifact.language);
        return;
      }
      
      // For JSON-based panels, parse the code
      // For raw code panels, use artifact.code directly
      try {
        const data = JSON.parse(artifact.code);
        renderContent(data);
      } catch (e) {
        document.getElementById('content').innerHTML = 
          `<div class="error">Failed to parse: ${e.message}</div>`;
      }
    }
    
    function renderContent(data) {
      document.getElementById('content').innerHTML = 
        `<pre>${JSON.stringify(data, null, 2)}</pre>`;
    }
    
    // Signal ready when panel loads
    sendToSDK('panel:ready', { timestamp: Date.now() });
  </script>
</body>
</html>

Using the Bridge Helper

For convenience, use @artifactuse/shared which handles the message protocol:
<script setup>
import { ref, onMounted } from 'vue';
import { createBridge } from '@artifactuse/shared/bridge';

const data = ref(null);
const theme = ref('dark');
let bridge = null;

onMounted(() => {
  bridge = createBridge({ debug: true });
  
  // Handle artifact loading (primary method)
  bridge.on('load:artifact', (artifact) => {
    console.log('Loading artifact:', artifact);
    
    if (artifact.language !== 'my-language') {
      console.warn('Unsupported language:', artifact.language);
      return;
    }
    
    // For JSON-based artifacts: sanitize and parse
    try {
      let code = artifact.code;
      // Fix unescaped newlines in JSON strings
      code = code.replace(/"([^"\\]*(\\.[^"\\]*)*)"/g, (match) => {
        return match.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
      });
      data.value = JSON.parse(code);
    } catch (e) {
      console.error('Failed to parse:', e);
    }
    
    // For raw code artifacts, use directly:
    // data.value = artifact.code;
  });
  
  // Handle theme changes
  bridge.on('theme:change', ({ theme: newTheme }) => {
    theme.value = newTheme;
  });
  
  // Signal ready
  bridge.signalReady();
});

// Send events to SDK
function submitForm(values) {
  bridge.send('form:submit', { 
    formId: 'my-form', 
    action: 'submit',
    values, 
    timestamp: Date.now() 
  });
}
</script>

JSON Sanitization

When parsing artifact.code that contains JSON, you may encounter unescaped newlines inside string values. This happens when the code extractor doesn’t properly escape literal newlines. Always sanitize before parsing:
function parseArtifactCode(code) {
  // Fix unescaped newlines inside JSON string values
  const sanitized = code.replace(/"([^"\\]*(\\.[^"\\]*)*)"/g, (match) => {
    return match.replace(/\n/g, '\\n')
                .replace(/\r/g, '\\r')
                .replace(/\t/g, '\\t');
  });
  
  return JSON.parse(sanitized);
}
Panels that need sanitization (parse JSON):
  • json-panel - displays JSON data
  • form-panel - parses form configuration
  • diff-panel - parses {oldCode, newCode} structure
  • editor-panel - parses canvas/video data
Panels that don’t need sanitization (raw code):
  • svg-panel - raw SVG markup
  • html-panel - raw HTML/Markdown
  • react-panel - raw JSX code
  • vue-panel - raw Vue SFC code
  • code-panel - raw JavaScript/Python

Theme Support

Panels should support both dark and light themes. The SDK sends theme updates via theme:change message.
bridge.on('theme:change', ({ theme, colors }) => {
  document.documentElement.setAttribute('data-theme', theme);
  
  // Apply custom colors if provided
  if (colors) {
    Object.entries(colors).forEach(([key, value]) => {
      document.documentElement.style.setProperty(`--color-${key}`, value);
    });
  }
});

CSS Variables

Use CSS variables for easy theming:
:root {
  --bg: #111827;
  --text: #f3f4f6;
  --border: #374151;
  --primary: #6366f1;
}

[data-theme="light"] {
  --bg: #ffffff;
  --text: #111827;
  --border: #e5e7eb;
  --primary: #4f46e5;
}

body {
  background: var(--bg);
  color: var(--text);
}

Registering Custom Panels

The SDK supports configurable panels that can be added, overridden, or disabled without modifying SDK source code.

Configuration Options

provideArtifactuse({
  // Base CDN URL (optional, defaults to Artifactuse CDN)
  cdnUrl: 'https://cdn.yourdomain.com',
  
  // Panel configuration
  panels: {
    // Add new panel type (uses cdnUrl)
    'chart': 'chart-panel',
    
    // Override with full URL (different CDN)
    'video': 'https://video-cdn.com/editor-panel',
    
    // Explicit CDN per panel
    'diagram': { path: 'diagram-panel', cdn: 'https://diagrams.example.com' },
    
    // Disable a built-in panel
    'canvas': null,
  }
})

Panel Configuration Formats

FormatExampleDescription
Relative path'chart-panel'Uses cdnUrl as base
Full URL'https://cdn.example.com/panel'Direct URL to panel
Object{ path: 'panel', cdn: 'https://...' }Explicit path + CDN
NullnullDisable the panel

Runtime Registration

Register panels at runtime with support for type aliases:
const { registerPanel, unregisterPanel, hasPanel, panelTypes } = useArtifactuse();

// Register single type
registerPanel('chart', 'chart-panel');

// Register multiple types/aliases at once
registerPanel(['python', 'py'], 'code-panel');
registerPanel(['typescript', 'ts', 'tsx'], 'code-panel');

// Check if panel exists
if (hasPanel({ type: 'chart' })) {
  console.log('Chart panel available');
}

// Get all registered types
console.log(panelTypes.value);

// Disable multiple panels
unregisterPanel(['canvas', 'whiteboard', 'drawing']);

How Panel Routing Works

When the SDK detects an artifact, it looks up the panel URL using:
  1. Artifact type (e.g., form, social)
  2. Code language (e.g., json, svg, python)
// Example: artifact with type "chart" 
// → loads https://cdn.yourdomain.com/chart-panel/

// Example: code block with language "json"
// → loads https://cdn.yourdomain.com/json-panel/

Full Example

import { provideArtifactuse, useArtifactuse } from 'artifactuse/vue';

// 1. Configure SDK with custom panels
provideArtifactuse({
  cdnUrl: 'https://cdn.example.com',
  panels: {
    // New panel types
    'chart': 'chart-panel',
    'chart-bar': 'chart-panel',
    'chart-line': 'chart-panel',
    
    // Override video to use different CDN
    'video': 'https://video-cdn.example.com/editor-panel/video',
    
    // Disable canvas
    'canvas': null,
  }
});

// 2. Register additional panels at runtime
const { registerPanel } = useArtifactuse();
registerPanel(['python', 'py'], 'code-panel');

// 3. AI can now generate chart artifacts
// ```chart
// { "type": "bar", "data": [...] }
// ```

Default Panels

The SDK includes these built-in panel mappings:
Type/LanguagePanel Path
formform-panel
video, videoeditor, timelineeditor-panel/video
canvas, whiteboard, drawingeditor-panel/canvas
jsonjson-panel
svgsvg-panel
diff, patchdiff-panel
javascript, js, python, pycode-panel
jsx, reactreact-panel
vuevue-panel
html, htm, markdown, mdhtml-panel
mermaidmermaid-panel

Complete Panel Template

Here’s a complete template for building a custom panel:
// my-panel/src/main.js
import { createBridge } from '@artifactuse/shared/bridge';

let bridge = null;
let currentData = null;

// Initialize panel
function init() {
  bridge = createBridge({ debug: true });
  
  // Primary: Handle artifact loading
  bridge.on('load:artifact', (artifact) => {
    console.log('Loading artifact:', artifact);
    
    // Validate language
    if (artifact.language !== 'my-language') {
      console.warn('Unsupported language:', artifact.language);
      return;
    }
    
    try {
      // For JSON-based panels: sanitize and parse
      let code = artifact.code;
      code = code.replace(/"([^"\\]*(\\.[^"\\]*)*)"/g, (match) => {
        return match.replace(/\n/g, '\\n')
                    .replace(/\r/g, '\\r')
                    .replace(/\t/g, '\\t');
      });
      currentData = JSON.parse(code);
      
      // For raw code panels, use directly:
      // currentData = artifact.code;
      
      render(currentData);
      console.log('Artifact loaded successfully');
      
    } catch (e) {
      console.error('Failed to parse artifact:', e);
      renderError(e.message);
    }
  });
  
  // Handle theme changes
  bridge.on('theme:change', ({ theme, colors }) => {
    document.documentElement.setAttribute('data-theme', theme);
  });
  
  // Signal ready - IMPORTANT: SDK waits for this before sending artifact
  bridge.signalReady();
  
  console.log('Panel initialized');
}

function render(data) {
  const container = document.getElementById('app');
  container.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
}

function renderError(message) {
  const container = document.getElementById('app');
  container.innerHTML = `<div class="error">${message}</div>`;
}

// Send data back to SDK
function notifyUpdate(data) {
  bridge.send('artifact:update', { data });
}

// Initialize when ready
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

Resources