Skip to content

MkDocs Integration Guide

Embed interactive Panel and Python examples in your MkDocs documentation using panel-live.

Prerequisites

Installation

pip install panel-live

Configuration

For a working reference example checkout panel-extensions/panel-live/zensical.toml.

Configure a Custom Fence

Add the custom fence to your mkdocs.yml:

markdown_extensions:
  - pymdownx.superfences:
      custom_fences:
        - name: panel
          class: panel-live
          validator: !!python/name:panel_live.fences.validator
          format: !!python/name:panel_live.fences.formatter

Or if using zensical (TOML-based MkDocs config):

[project.markdown_extensions.pymdownx.superfences]
custom_fences = [
  { name = "panel", class = "panel-live", validator = "panel_live.fences.validator", format = "panel_live.fences.formatter" }
]

Loading the Web Component

Add the panel-live JavaScript and CSS to your site. You can use the CDN:

extra_javascript:
  - https://cdn.jsdelivr.net/npm/panel-live@latest/dist/panel-live.js
extra_css:
  - https://cdn.jsdelivr.net/npm/panel-live@latest/dist/panel-live.css

Or copy the files locally (recommended for reproducibility):

extra_javascript:
  - assets/js/panel-live.js
extra_css:
  - assets/css/panel-live.css

Fence Syntax

Basic

Use a fenced code block with the panel language identifier:

```panel
import panel as pn
pn.panel("Hello").servable()
```

With Attributes

Use the {.panel ...} syntax to pass attributes:

```{.panel mode="editor" theme="dark" height="500px"}
import panel as pn
pn.panel("Hello").servable()
```

Available Attributes

Attribute Values Default Description
mode app, editor, playground app Display mode
theme light, dark, auto auto Color theme
height CSS value (e.g. 500px) Fixed height
layout vertical, horizontal mode-dependent Editor/preview layout
auto-run true, false true Run code on page load
label any string Python Language pill label
code-visibility visible, hidden, collapsed visible Editor visibility
code-position first, last first Code panel position

Expression Mode (No .servable())

Code without .servable() uses the expression branch — the last expression is rendered as output. This works with any displayable Python object:

```{.panel mode="editor" label="Python"}
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
fig
```

Cross-Origin Headers (COOP/COEP)

Pyodide requires SharedArrayBuffer, which needs these HTTP headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Local Development

The panel-live dev server (pixi run serve or python serve.py) adds these headers automatically.

GitHub Pages

GitHub Pages does not support custom headers. Workarounds:

  1. Service Worker approach — Use coi-serviceworker to enable COOP/COEP via a service worker
  2. Cloudflare Pages — Supports custom headers via _headers file

Netlify

Add a _headers file to your docs directory:

/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

Other Hosts

Most hosting providers support custom headers via configuration files or server settings. Consult your provider's documentation.

mini-coi (No Server Configuration Required)

If you cannot configure COOP/COEP headers on your hosting provider (e.g., GitHub Pages), mini-coi provides SharedArrayBuffer support via a lightweight service worker:

  1. Download mini-coi.js to your docs directory
  2. Add it to mkdocs.yml before panel-live.js:
extra_javascript:
  - mini-coi.js
  - https://cdn.jsdelivr.net/npm/panel-live@latest/dist/panel-live.js

mini-coi registers a service worker that adds COOP/COEP headers transparently. This avoids the need for server-side configuration and works on any static hosting provider.

When to use mini-coi:

  • GitHub Pages (no custom header support)
  • Static hosting without server configuration access
  • Quick local testing without serve.py

Note: If you control the server, native HTTP headers (see above) are preferred as they are more robust and don't require JavaScript.

Troubleshooting

"SharedArrayBuffer is not defined"

Missing COOP/COEP headers. See the Cross-Origin Headers section.

Code blocks render as plain text

Ensure pymdownx.superfences is configured with the custom fence and that panel-live.js is loaded on the page.

Pyodide fails to load

Check browser console for errors. Common causes:

  • Ad blockers or tracking prevention blocking CDN resources
  • Corporate proxy blocking WebAssembly downloads
  • Outdated browser without WebAssembly support

Multiple panel-live blocks on one page

All blocks share a single Pyodide runtime. The first block to initialize loads Pyodide; subsequent blocks reuse it. Execution is serialized (one at a time) to avoid conflicts.