npm package: @rizalibnu/sandpack-embedder
Description: A universal HTML-based Sandpack runtime — dynamically inject interactive code editors and previews into any HTML document.
Author: Rizal Ibnu
Latest version: 1.6.4

# Sandpack Embedder

> Turn code snippets generated by a CMS editor into live Sandpack playgrounds.

![Sandpack Embedder](sandpack-embedder-cover.jpg)

Want to see Sandpack Embedder in action?

👉 [Live Demo](https://rizalibnu.dev/i-built-sandpack-embedder-turn-code-snippets-into-live-playgrounds-without-touching-your-cms/)

## What Sandpack Embedder Does

**Sandpack Embedder** parses these code snippets and converts them into **live Sandpack playgrounds**, without needing to modify your CMS core.

It:

1. Finds elements containing `<sandpack>`-like markup.
2. Extracts and decodes escaped HTML.
3. Parses props like `template="react"` and `options="{'editorHeight':400}"`.
4. Creates a live **Sandpack React playground** inside your CMS-rendered page.

---

## Example

### Input (Code snippets from CMS)

````md
<sandpack
  template="react"
  custom-setup='{
    "dependencies": {
      "react": "19.2.0",
      "react-dom": "19.2.0"
    }
  }'
>
```js /App.js
export default function App() {
  return <h1>Hello world</h1>
}
```
```js /hidden-file.js hidden
console.log("I'm  hidden");
```
</sandpack>
````

### Output (after running `SandpackEmbedder`)

💡 **Turns into a live playground!**

```tsx
<Sandpack
  files={{
    "/index.js": `
      import "./styles.css";

      document.getElementById("app").innerHTML = `
      <h1>Hello Sandpack</h1>
      `;
    `,
    "/hidden-file.js": {
      code: `console.log("I'm  hidden");`,
      hidden: true
    }
  }}
  template="react"
  customSetup={{
    dependencies: {
      react: "19.2.0",
      "react-dom": "19.2.0"
    }
  }}
/>
```

---

## Usage

You can use `SandpackEmbedder` on **any web page** that includes HTML code blocks.

### Simple HTML Integration

```html
<pre><code>// code snippets</code></pre>
<script type="module">
  import { SandpackEmbedder } from "https://esm.sh/@rizalibnu/sandpack-embedder";

  const sandpack = new SandpackEmbedder({
    codeSelector: "pre > code",
    playgroundClass: "sandpack-playground",
  });

  sandpack.load();
</script>
```

### Using Import Maps

```html
<script type="importmap">
  {
    "imports": {
      "@rizalibnu/sandpack-embedder": "https://esm.sh/@rizalibnu/sandpack-embedder"
    }
  }
</script>
<script type="module">
  import { SandpackEmbedder } from "@rizalibnu/sandpack-embedder";
</script>
```

---

## Options

You can customize selectors, theme, or even replace the Sandpack component.

```ts
export interface SandpackEmbedderOptions {
  /** CSS selector used to find code elements containing escaped <Sandpack> markup. Default: 'pre > code' */
  codeSelector?: string;

  /** CSS class name applied to the mount container created for each playground embed. Default: 'sandpack' */
  playgroundClass?: string;

  /** Custom Sandpack React components (keyed by name, e.g. "Sandpack"). */
  customComponents?: Record<string, Component>;

  /** Sandpack initial theme. Default theme by Sandpack Component used when not specified by props. */
  theme?: string | SandpackProps['theme'];

  /** Custom Sandpack Theme (keyed by name, e.g. "amethyst").  */
  customThemes?: Record<string, DeepPartial<SandpackTheme>>;

  /**
   * Selector or callback that determines where to inject the Sandpack playground.
   * - If string → resolved via `closest(selector)` from code block
   * - If function → return a DOM element relative to the code block
   *
   * Default: codeSelector's parentElement
   */
  injectTarget?: string | ((codeEl: HTMLElement) => HTMLElement | null);

  /** Controls where the mount node is injected relative to injectTarget. Default: 'after' */
  injectPosition?: 'before' | 'after' | 'replace' | 'inside';

  /** Controls visibility of the original code snippet. Default: 'false' */
  showOriginalCode?: boolean;
}
```

---

## Updating Embedder

You can change the Sandpack theme dynamically via:

```js
sandpack.updateTheme("light");
```

Later when DOM updates or navigation changes:

```js
sandpack.refresh();
```

Completely clean up:

```js
sandpack.destroy();
```

---

## Preset Components

Sandpack Embedder includes two built-in presets that let you display only the preview or only the code viewer—perfect for documentation.

These presets wrap your code fences and render a specific Sandpack layout:

`<preview>` → Preview-only (no editor)

`<code-viewer>` → Code Viewer only (readonly, no preview)

Use them exactly like your `<sandpack>` tag, but simplified.

```md
<preview template="react">
// markdown code blocks...
<preview>

<code-viewer template="react" options='{"viewerHeight": 500}'>
// markdown code blocks...
<code-viewer>
```

---

## Advanced

### Control where playground is injected

You can let users decide where the live playground is inserted relative to the `<code>` element:

```js
new SandpackEmbedder({
  codeSelector: ".language-sandpack",
  injectTarget: ".code-wrapper",
  injectPosition: "after", // "after" | "before" | "replace" | "inside"
}).load();
```

Use a function for full control

```js
new SandpackEmbedder({
  codeSelector: ".language-sandpack",
  injectTarget: (el) => el.closest(".code-wrapper")?.querySelector("pre"),
  injectPosition: "inside"
});

```

### Custom Theme Mapping

You can override the theme or add your own:

```js
import { amethyst, aquaBlue } from "@codesandbox/sandpack-themes";
const sandpack = new SandpackEmbedder({
    customThemes: { amethyst, aquaBlue, neoCyan },
    theme: "amethyst"
}).load();

sandpack.updateTheme("aquaBlue");
```

````md
<sandpack theme="neoCyan">
  // markdown code blocks...
</sandpack>
````

### Custom Component Mapping

You can override the `<sandpack>` component or add your own:

```html
<script type="importmap">
  {
    "imports": {
      "@codesandbox/sandpack-react": "https://esm.sh/@codesandbox/sandpack-react@2.20.0",
      "@rizalibnu/sandpack-embedder": "https://esm.sh/@rizalibnu/sandpack-embedder"
    }
  }
</script>
<script type="module" src="https://esm.sh/tsx"></script>
<script type="text/babel">
  import { SandpackEmbedder } from "@rizalibnu/sandpack-embedder";
  import { 
    SandpackProvider, 
    SandpackLayout, 
    SandpackPreview, 
    SandpackCodeEditor 
  } from "@codesandbox/sandpack-react";

  function CustomSandpack(props) {
    return (
      <SandpackProvider>
        <SandpackLayout>
          <SandpackCodeEditor />
          <SandpackPreview />
        </SandpackLayout>
      </SandpackProvider>
    )
  };

  function VuePack(props) {
    return <Sandpack {...props} template="vue" />;
  }

  new SandpackEmbedder({
    customComponents: { sandpack: CustomSandpack, vuepack: VuePack },
  }).load();
</script>
```

This allows you to use any custom tag like `<vuepack>` or `<playground>` as long as it’s mapped in customComponents.

````md
<vuepack>
  // markdown code blocks...
</vuepack>
````

---

## Installation

```bash
pnpm add @rizalibnu/sandpack-embedder
# or
yarn add @rizalibnu/sandpack-embedder
# or
npm install @rizalibnu/sandpack-embedder
```

## Storybook

To explore and test UI behaviors interactively:

```bash
pnpm storybook
```

## Testing

Run unit tests with Vitest:

```bash
pnpm test
```

---

## License

MIT © [Rizal Ibnu](https://github.com/rizalibnu)