Embedding
An embed runs Bun code in your page, in the visitor’s browser. Nothing runs on a server. This page shows the options; the full list is in the embed reference, and the configurator builds a snippet from them with a live preview.
This one is live. Click it to start:
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
db.run("create table t (n integer)");
for (const n of [1, 2, 3]) db.run("insert into t values (?)", [n]);
console.log(db.query("select sum(n) as total from t").get());Embed with the element
Section titled “Embed with the element”Load the script once, then use <bun-playground>:
<script type="module" src="https://runbun.dev/embed.js"></script>
<bun-playground mode="code"> <script type="text/plain" data-file="index.ts"> console.log("hello from Bun"); </script></bun-playground>The code runs when the element comes into view and the page is ready. Add load="click" to wait for a click on its Run button, and put a slot="poster" child inside to show something until it starts:
<bun-playground load="click" mode="preview" example="elysia-demo"> <img slot="poster" src="/elysia.png" alt="Elysia demo" /></bun-playground>Embed with an iframe
Section titled “Embed with an iframe”No script needed. The URL parameters have the same names as the attributes:
<iframe src="https://runbun.dev/embed?mode=preview&example=elysia-demo" allow="cross-origin-isolated; clipboard-write" style="width: 100%; height: 420px; border: 0"></iframe>A share link’s #code=… hash works too: https://runbun.dev/embed?mode=code#code=…. An iframe doesn’t resize itself; listen for the resize event (see scripting) or set a height.
Pick a mode
Section titled “Pick a mode”| Mode | Shows | Loads | Default height |
|---|---|---|---|
code | The editor and a console below it. | wasm for the transpiler and resolver (about 4 MB, once per site) | fits the code |
console | The editor beside the console, which takes bun test and other commands. | same | 360px |
preview | The editor with file tabs beside the preview. Installs missing packages. | same, plus the packages | 420px |
full | The whole playground: file tree, tabs, preview over console, Format, Install. Waits for Run. | same, plus the formatter (7 MB) once used | 600px |
result | Only the preview, with an Edit button that switches to preview. | same | 420px |
Every mode except full runs the code once it has loaded; autorun="false" waits for Run. While the program runs, saving an edit restarts it, like bun --watch (features="-watch" turns that off).
Set height to any CSS length, or auto to fit the code.
Add files
Section titled “Add files”Each <script type="text/plain" data-file="path"> is one file. The text is dedented, so you can indent it with the HTML around it. Write <\/script for a literal </script inside a file.
<bun-playground mode="full" file="src/index.ts"> <script type="text/plain" data-file="src/index.ts"> import { greet } from "./greet"; console.log(greet("Bun")); </script> <script type="text/plain" data-file="src/greet.ts"> export const greet = (name: string) => `Hello, ${name}`; </script></bun-playground>Other sources: example="<name>" (a bundled example), repo="owner/repo" (a public GitHub repo), or share="#code=…".
In these docs, a fence marked playground becomes an embed. Consecutive fences with the same group=<name> become one embed with a tab per file:
```ts playground group=demo title="index.ts"import { greet } from "./greet";```
```ts playground group=demo title="greet.ts"export const greet = (n: string) => n;```To get the same in your own Astro site, see Astro.
Turn features on and off
Section titled “Turn features on and off”features is a comma list added to the mode’s defaults. A leading - turns one off and locks it, so readers can’t turn it back on.
<bun-playground mode="code" features="types,-open"></bun-playground>Readers get a ⋯ menu to show or hide the file tree, tabs, console and preview, and to turn on type hints, formatting, restart on save and the Auto-save switch. It only offers what you haven’t locked. With format or install on, the menu also has Format file and bun install; Reset loads the original files again. Open in runbun.dev opens the current files as a share link. The feature list is in the reference.
Readers can drag the borders between panes (or focus one and use the arrow keys) to resize them; a double click goes back to the default. Sizes are kept for the browser tab.
Type checking downloads 10 MB the first time it’s used, and format 7 MB. They are off until someone turns them on.
Edits are lost on reload. Set persist="some-key" to keep them in the reader’s browser, or readonly to stop editing (Run still works).
The run status shows as a toast over the panes that fades once the program settles; status="bar" shows it as a pill in the bar instead.
Open in a modal
Section titled “Open in a modal”expand adds a button to the embed’s bar that opens it in a modal over your page. The program keeps running, and edits stay. Give it a mode to switch to while it’s open, so a small snippet becomes the whole playground:
<bun-playground mode="code" expand="full">…</bun-playground>Escape or the button closes it. Your own controls can call el.expand() and el.collapse(). The code blocks in these docs open as full; write expand=false in a fence’s meta to leave the button out.
Load and sizing
Section titled “Load and sizing”load | The embed starts |
|---|---|
visible (default) | When it scrolls into view |
eager | At once |
click | When the reader clicks the poster’s Run button |
Prefer click for pages with several embeds, or for modes that download a lot.
theme="auto" follows the visitor’s system setting. Use light, dark, or a theme family’s light variant such as github-light. To follow your own page’s theme, set the attribute when it changes. These docs do that with a small script.
family picks a family without touching the color mode, so theme="light"/theme="dark" can follow your page while the family stays put:
<bun-playground family="github-light" fonts="https://fonts.googleapis.com/css2?family=Inter&display=swap"></bun-playground>vars brings the embed’s colors in line with your page. It sets CSS custom properties on top of the theme’s, on both schemes or one:
<bun-playground vars='{"base":{"--accent":"#ff5a5f","--radius":"1rem","--font-ui":"Inter, sans-serif","--font-mono":"JetBrains Mono, monospace"}}' fonts="https://fonts.googleapis.com/css2?family=Inter&family=JetBrains+Mono&display=swap"></bun-playground>fonts takes stylesheet URLs (whitespace-separated) and loads them inside the embed; --font-ui and --font-mono are what the chrome and the code use. When your page’s own theme changes, call el.setVars(vars) with the new values; the object’s shape and the variables are in the reference.
Defaults for a page
Section titled “Defaults for a page”With several embeds on a page, set what they share once with <bun-playground-defaults>. Each embed’s own attributes still win:
<bun-playground-defaults family="github-light" load="click" expand="full" theme="dark"></bun-playground-defaults>
<bun-playground mode="preview">…</bun-playground><bun-playground mode="code" expand="false">…</bun-playground>To follow your page’s theme, set theme (or vars) on the defaults element; every embed that doesn’t set its own updates in place. Keys, precedence and how vars merges are in the reference.
Script it
Section titled “Script it”<bun-playground id="p" mode="console" autorun="false"> <script type="text/plain" data-file="index.ts">console.log(1 + 1);</script></bun-playground>
<script type="module"> const el = document.getElementById("p"); el.addEventListener("bun-playground:console", (e) => console.log(e.detail.text)); await el.ready; await el.writeFile("index.ts", "console.log(40 + 2);"); await el.run();</script>Every method and event is in the reference.
Limits
Section titled “Limits”- Browsers. Embeds need WebAssembly Memory64: Chrome and Edge 133+, Firefox 134+. Safari doesn’t have it yet. There the embed still shows and edits the code, says why it can’t run it, and sends
unsupported. See Browsers. - Vite and Rolldown. Projects that bundle with Vite or Rolldown need
SharedArrayBuffer, so cross-origin isolation. An iframe is isolated only if your page is too (it sendsCOOP: same-originandCOEP: credentialless) and the iframe hasallow="cross-origin-isolated". These docs aren’t isolated, so such examples run only on runbun.dev. Everything else works in any iframe. - Storage. The service worker and saved files are partitioned per host site. The first visit on each site downloads the wasm again. Embeds on one page are independent: each has its own files, program and preview.