> ## Documentation Index
> Fetch the complete documentation index at: https://artifactuse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Panels

> Build your own panel artifacts

## 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.

<Card title="Panels Repository" icon="github" href="https://github.com/artifactuse/panels">
  View source code, development docs, and contribution guidelines
</Card>

## Official Panels

| Package                     | Description                         |
| --------------------------- | ----------------------------------- |
| `@artifactuse/json-panel`   | Interactive JSON tree viewer        |
| `@artifactuse/svg-panel`    | SVG preview with pan/zoom           |
| `@artifactuse/diff-panel`   | Side-by-side diff comparison        |
| `@artifactuse/html-panel`   | HTML + Markdown preview             |
| `@artifactuse/react-panel`  | React/JSX preview                   |
| `@artifactuse/vue-panel`    | Vue SFC preview                     |
| `@artifactuse/form-panel`   | Interactive forms, wizards          |
| `@artifactuse/sheet-panel`  | CSV/TSV spreadsheet viewer & editor |
| `@artifactuse/editor-panel` | Canvas + Video editor               |
| `@artifactuse/code-panel`   | JS + Python code execution          |

## 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:

```javascript theme={null}
// 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:

| Panel          | Language Values                    | Code Format                          | Needs JSON Parse |
| -------------- | ---------------------------------- | ------------------------------------ | ---------------- |
| `json-panel`   | `json`                             | JSON string to display               | ✅ Yes            |
| `form-panel`   | `form`                             | JSON form configuration              | ✅ Yes            |
| `diff-panel`   | `smartdiff`                        | JSON: `{oldCode, newCode, language}` | ✅ Yes            |
| `editor-panel` | `canvas`, `video`                  | JSON: `{width, height, shapes}`      | ✅ Yes            |
| `svg-panel`    | `svg`                              | Raw SVG markup                       | ❌ No             |
| `html-panel`   | `html`, `markdown`, `md`           | Raw HTML/Markdown                    | ❌ No             |
| `react-panel`  | `react`, `jsx`                     | Raw JSX code                         | ❌ No             |
| `vue-panel`    | `vue`                              | Raw Vue SFC code                     | ❌ No             |
| `code-panel`   | `javascript`, `js`, `python`, `py` | Raw code                             | ❌ No             |

### SDK → Panel: Theme Updates

The SDK sends theme updates when the user changes themes:

```javascript theme={null}
{
  type: 'artifactuse',
  action: 'theme:change',
  data: {
    theme: 'dark',        // 'dark' or 'light'
    colors: { ... }       // Optional custom colors
  }
}
```

### Panel → SDK

```javascript theme={null}
// 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

<Steps>
  <Step title="Create project">
    Set up a project with your preferred framework (React, Vue, Svelte, vanilla JS)
  </Step>

  <Step title="Signal ready">
    Send `panel:ready` message when your panel loads
  </Step>

  <Step title="Handle load:artifact">
    Listen for `load:artifact` message and render the artifact content
  </Step>

  <Step title="Handle theme changes">
    Listen for `theme:change` message and update your UI
  </Step>

  <Step title="Send events">
    Emit events back to the SDK when user interacts
  </Step>

  <Step title="Deploy">
    Host on your CDN and configure SDK to use it
  </Step>
</Steps>

### Vanilla JavaScript

```html theme={null}
<!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:

<Tabs>
  <Tab title="Vue">
    ```vue theme={null}
    <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>
    ```
  </Tab>

  <Tab title="React">
    ```jsx theme={null}
    import { useEffect, useState } from 'react';
    import { createBridge } from '@artifactuse/shared/bridge';

    function Panel() {
      const [data, setData] = useState(null);
      const [theme, setTheme] = useState('dark');
      const [bridge, setBridge] = useState(null);
      
      useEffect(() => {
        const b = createBridge({ debug: true });
        setBridge(b);
        
        // Handle artifact loading (primary method)
        b.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;
            code = code.replace(/"([^"\\]*(\\.[^"\\]*)*)"/g, (match) => {
              return match.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
            });
            setData(JSON.parse(code));
          } catch (e) {
            console.error('Failed to parse:', e);
          }
        });
        
        // Handle theme changes
        b.on('theme:change', ({ theme }) => setTheme(theme));
        
        // Signal ready
        b.signalReady();
        
        return () => b.destroy();
      }, []);
      
      return (
        <div className={theme}>
          {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Loading...'}
        </div>
      );
    }
    ```
  </Tab>

  <Tab title="Svelte">
    ```svelte theme={null}
    <script>
    import { onMount, onDestroy } from 'svelte';
    import { createBridge } from '@artifactuse/shared/bridge';

    let data = null;
    let theme = 'dark';
    let bridge;

    onMount(() => {
      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;
          code = code.replace(/"([^"\\]*(\\.[^"\\]*)*)"/g, (match) => {
            return match.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
          });
          data = JSON.parse(code);
        } catch (e) {
          console.error('Failed to parse:', e);
        }
      });
      
      // Handle theme changes
      bridge.on('theme:change', ({ theme: t }) => { theme = t; });
      
      // Signal ready
      bridge.signalReady();
    });

    onDestroy(() => bridge?.destroy());
    </script>

    <div class={theme}>
      {#if data}
        <pre>{JSON.stringify(data, null, 2)}</pre>
      {:else}
        Loading...
      {/if}
    </div>
    ```
  </Tab>
</Tabs>

## 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:**

```javascript theme={null}
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.

```javascript theme={null}
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:

```css theme={null}
: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

```javascript theme={null}
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

| Format        | Example                                 | Description           |
| ------------- | --------------------------------------- | --------------------- |
| 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   |
| Null          | `null`                                  | Disable the panel     |

### Runtime Registration

Register panels at runtime with support for type aliases:

<Tabs>
  <Tab title="Vue">
    ```javascript theme={null}
    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']);
    ```
  </Tab>

  <Tab title="React">
    ```javascript theme={null}
    import { usePanelRegistry } from 'artifactuse/react';

    function PluginLoader() {
      const { register, unregister, isRegistered, types } = usePanelRegistry();
      
      useEffect(() => {
        // Register with aliases
        register(['python', 'py'], 'code-panel');
        
        return () => unregister(['python', 'py']);
      }, []);
      
      return <div>Panels: {types.join(', ')}</div>;
    }
    ```
  </Tab>

  <Tab title="Svelte">
    ```javascript theme={null}
    const { registerPanel, unregisterPanel, panelTypes } = getArtifactuseContext();

    // Register with aliases
    registerPanel(['python', 'py'], 'code-panel');

    // Reactive store of panel types
    $: console.log($panelTypes);

    // Disable panels
    unregisterPanel(['canvas', 'whiteboard']);
    ```
  </Tab>

  <Tab title="Vanilla JS">
    ```javascript theme={null}
    const sdk = createArtifactuse({
      panels: {
        'chart': 'chart-panel',
      }
    });

    // Runtime registration
    sdk.registerPanel(['python', 'py'], 'code-panel');

    // Check availability
    if (sdk.hasPanel({ language: 'py' })) {
      const url = sdk.getPanelUrl({ language: 'py' });
    }

    // Get all types
    console.log(sdk.getPanelTypes());
    ```
  </Tab>
</Tabs>

### 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`)

```javascript theme={null}
// 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

````javascript theme={null}
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/Language                      | Panel Path            |
| ---------------------------------- | --------------------- |
| `form`                             | `form-panel`          |
| `video`, `videoeditor`, `timeline` | `editor-panel/video`  |
| `canvas`, `whiteboard`, `drawing`  | `editor-panel/canvas` |
| `json`                             | `json-panel`          |
| `svg`                              | `svg-panel`           |
| `diff`, `patch`, `smartdiff`       | `diff-panel`          |
| `javascript`, `js`, `python`, `py` | `code-panel`          |
| `jsx`, `react`                     | `react-panel`         |
| `vue`                              | `vue-panel`           |
| `html`, `htm`, `markdown`, `md`    | `html-panel`          |
| `mermaid`                          | `mermaid-panel`       |

## Complete Panel Template

Here's a complete template for building a custom panel:

```javascript theme={null}
// 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

<CardGroup cols={2}>
  <Card title="Source Code" icon="github" href="https://github.com/artifactuse/panels">
    Browse official panel implementations
  </Card>

  <Card title="Development Guide" icon="book" href="https://github.com/artifactuse/sdk/blob/main/docs/DEVELOPING.md">
    Step-by-step guide to building panels
  </Card>

  <Card title="Shared Utilities" icon="package" href="https://github.com/artifactuse/panels/tree/main/packages/shared">
    Bridge, theme, and component utilities
  </Card>

  <Card title="Contributing" icon="git-pull-request" href="https://github.com/artifactuse/sdk/blob/main/CONTRIBUTING.md">
    Contribute to official panels
  </Card>
</CardGroup>
