---
title: "Run code"
description: "Add a Run code button that runs the example in the browser and shows the output under the block."
url: "https://ewels.github.io/starlight-codeblocks/features/run-code/"
markdown: "https://ewels.github.io/starlight-codeblocks/features/run-code.md"
section: "Copy and run"
site: "starlight-codeblocks documentation"
context: "This page is from the documentation of starlight-codeblocks. A Starlight plugin that adds focus, line states, annotations, API auto-linking and 22 more features to the code blocks of a site."
index: "https://ewels.github.io/starlight-codeblocks/llms.txt"
---

# Run code

> Add a Run code button that runs the example in the browser and shows the output under the block.

A code block in Starlight shows code, but readers must copy it to a terminal to see what it does. The `runnable` attribute adds a **Run code** button under the block. The button runs the code in the browser and shows the output above the button.

````md
```py runnable
from statistics import mean, stdev

reads = [1520, 1610, 1480, 1575]
print(f"mean {mean(reads):.0f}, sd {stdev(reads):.1f}")
```
````

Click **Run code**. The first time, the browser downloads Python (Pyodide, a build of Python for the browser), which takes a few seconds. The output panel shows "Loading the Python runtime…" until it is ready, then the output of the code.

## Syntax

| Syntax | Where |
|---|---|
| `runnable` | Code block fence line |
| `runnable.label="<text>"`, `runnable.againLabel="<text>"` | Code block fence line |
| `runnable.timeout=<ms>` | Code block fence line |
| `runnable.packages="<name> <name>"` | Code block fence line |
| `runnable.button="below"`, `runnable.button="title"`, `runnable.button="both"` | Code block fence line |
| `[!output]`, `[!output end]` | Comment in the code, on its own line |
| `[!wait <ms>]` | Comment at the end of a line of output |
| `runnable.output="<text>"`, `runnable.outputDelay=<ms>` | Code block fence line |

### Languages

The language of the block chooses the runtime. Three languages work with no set-up:

| Language | Fence language | Runtime |
|---|---|---|
| Python | `py`, `python`, `pycon` | [Pyodide](https://pyodide.org/), a build of Python for the browser. It downloads from a CDN on the first click. |
| JavaScript | `js`, `javascript`, `mjs`, `cjs` | The browser's own JavaScript engine, in a web worker. |
| TypeScript | `ts`, `typescript`, `mts`, `cts` | The JavaScript runtime, after [Sucrase](https://sucrase.io/) removes the types. |

- For other languages, a site adds a runtime in the [`runtimes` option](#options). The guide to [adding a runtime](https://ewels.github.io/starlight-codeblocks/extend/add-a-runtime/) shows how.
- A site runtime for Python, JavaScript or TypeScript replaces the built-in one.
- Sites without Starlight add each runtime themselves, as in [Without Starlight](https://ewels.github.io/starlight-codeblocks/reference/expressive-code-plugins/#without-starlight).

In JavaScript and TypeScript, `console.log()` and `console.info()` go to standard output. `console.error()` and `console.warn()` go to standard error. The code runs as the body of an `async` function, so `await` works at the top level. The code cannot use `import` statements.

The TypeScript runtime removes the types and does not check them. Code with a type error runs, as it would with `tsx` or Node.js. The runtime downloads Sucrase (about 50 kB) on the first click of a TypeScript block.

### Python packages

The Python runtime reads the `import` lines of the code before it runs it, and installs the packages they need:

1. Packages that are part of Pyodide install from the Pyodide CDN. Pyodide has about 350 packages, such as NumPy, pandas, SciPy and scikit-learn. The [Pyodide package list](https://pyodide.org/en/stable/usage/packages-in-pyodide.html) has them all.
2. Other imports install from [PyPI](https://pypi.org/) with [micropip](https://micropip.pyodide.org/), under the name of the import. This works for pure Python packages: packages with a wheel that ends in `py3-none-any.whl`.

When the package name is not the import name, add `runnable.packages` to the fence line. For example, `import slugify` needs the `python-slugify` package. Without the attribute, the runtime would install the package called `slugify`, which is a different one. Separate several packages with spaces. A name can have a version, such as `tabulate==0.9.0`. The packages install before the code runs, and a package that does not install shows an error in the panel.

For code that cannot run in the browser, such as a command line tool, write its output in the block. The lines after `[!output]` are the output. They leave the code, and the button prints them a line at a time, as if the code ran. `[!output end]` ends the output, for code after it. Without it, the output goes to the end of the block.

The block needs no `runnable` attribute and no runtime, so it works in every language. `[!wait <ms>]` on a line of output waits that long before the next line.

`runnable.output` on the fence line does the same, with `\n` for each new line.

## Examples

### JavaScript, with standard error

`console.log()` goes to standard output and `console.error()` to standard error:

````md
```js runnable
const sizes = [3, 1, 4, 1, 5];
const total = sizes.reduce((sum, size) => sum + size, 0);
console.log('Total:', total);
console.error('Sizes of 1 are deprecated.');
```
````

### TypeScript

A TypeScript block runs after the runtime removes the types:

````md
```ts runnable
interface Sample {
  id: string;
  reads: number;
}

const samples: Sample[] = [
  { id: 'S1', reads: 1520 },
  { id: 'S2', reads: 1610 },
];
const total = samples.reduce((sum: number, s: Sample) => sum + s.reads, 0);
console.log(`${samples.length} samples, ${total} reads`);
```
````

### A package that Pyodide does not have

Tabulate is not part of Pyodide, so the runtime installs it from PyPI. The import name is the package name, so the block needs no attribute:

````md
```py runnable
from tabulate import tabulate

print(tabulate([["S1", 1520], ["S2", 1610]], headers=["Sample", "Reads"]))
```
````

### A package with another name

The import is `slugify`, but the package is `python-slugify`, so `runnable.packages` names it:

````md
```py runnable runnable.packages="python-slugify"
from slugify import slugify

print(slugify("Hello, World: Run Code!"))
```
````

### An error

An error in Python shows the traceback, from the line of the example that failed:

````md
```py runnable
counts = {"passed": 3, "failed": 0}
print(counts["passed"] / counts["failed"])
```
````

### The timeout

Code that never ends stops at the timeout. This loop waits for a value that never changes, so the panel shows the timeout message after 5 seconds:

````md
```js runnable
let ready = false;
while (!ready) {}
```
````

### Scripted output

This command cannot run in the browser, so the block has the output that the command prints. The lines show one at a time, 200 milliseconds apart, with a longer wait while the task runs:

````md
```sh
nextflow run hello.nf
# [!output]
N E X T F L O W  ~  version 25.04.0
Launching hello.nf [nice_curie] DSL2
executor >  local (3) # [!wait 1500]
[4e/8a1f2c] sayHello (3) | 3 of 3 ✔
Hello world!
Bonjour le monde!
Hola mundo!
```
````

### Output only

A block with only output shows the button in the middle of the empty code area. The button fades out when a reader clicks it, and the output takes its place:

````md
```sh
# [!output]
Pulling nextflow-io/hello ...
 downloaded from https://github.com/nextflow-io/hello.git # [!wait 1000]
Hello world!
```
````

### The button in the title bar

`runnable.button="title"` puts the button in the title bar, as the copy button is. `both` shows the button in both places:

````md
```py runnable runnable.button="title"
print(sorted(["chr2", "chr10", "chr1"], key=lambda c: int(c[3:])))
```
````

## Behaviour

- Running code:
  - The button is under the block, in the style of the [code walkthrough](https://ewels.github.io/starlight-codeblocks/features/code-walkthrough/) buttons. The `button` option moves it to the title bar, or shows it in both places.
  - The runtime loads only when a reader clicks **Run code** for the first time. A page with runnable blocks loads a small script, and no runtime.
  - After the first run, the button reads **Run again**. Each run replaces the output of the run before it. The `label` and `againLabel` options change the two texts, for example for a site in another language.
  - The code that runs is the copied text of the block. Directives are removed, and hidden lines are kept. The values that readers type into [fill-in placeholders](https://ewels.github.io/starlight-codeblocks/features/fill-in-placeholders/) are part of it too. For a Python session with `>>>` prompts, only the commands run, as **Copy commands** copies them. They run as in the Python REPL, so the output shows the value of each expression.
  - Every runtime runs the code in a web worker, so the page stays responsive while code runs. Each run starts with no names from earlier runs. In Python, `__name__` is `"__main__"`.
  - In JavaScript and TypeScript, a run ends when the top-level code is done. Output from a timer or a callback that the code does not `await` is lost.
  - A run stops after 10 seconds, with a message in the panel. The download of the runtime and of the packages that the code imports does not count towards this time.
- Scripted output:
  - The panel shows "Running…", then the lines of output, with `outputDelay` milliseconds (200 by default) before each line. With `0`, all the lines show at once. `[!wait]` changes the wait after one line.
  - Scripted output shows as standard output. The button does not run the code, and the block needs no runtime.
  - The output lines are not part of the code that readers see or copy.
  - A block with only output has no copy button. Its **Run code** button is in the middle of the empty code area, and fades out on the first click. Keyboard focus then moves to the output.
- Output:
  - The output panel has the label "Output". Standard output shows in green. Standard error shows in red, with a bar at its start, so the colour is not the only difference.
- Keyboard and screen readers:
  - The output panel is a polite live region. Screen readers announce the loading message and the output when they change.
  - The button keeps the keyboard focus during a run, so readers can run the code again with <kbd>Enter</kbd> or <kbd>Space</kbd>.
- Print:
  - The button does not print.
- Without JavaScript:
  - The button is hidden.

## Options

### `runnable`

Adds a button that runs the code in the browser. Set it to `false` to turn the feature off. `runnable` attributes then have no effect.

- Type: `false | object`
- Default: On

### `runnable.runtimes`

Runtime modules by language: a package path, or a path from the project root. The site entries are added to the built-in Python, JavaScript and TypeScript runtimes, or replace them.

- Type: `Record<string, string>`
- Default: `python`, `javascript` and `typescript`

### `runnable.timeout`

Milliseconds before a run stops, from 1 to 2147483647, the largest delay browsers accept. A code block can set its own on its fence line.

- Type: `number`
- Default: `10000`

### `runnable.label`

The text of the button. A code block can set its own on its fence line.

- Type: `string`
- Default: `'Run code'`

### `runnable.againLabel`

The text of the button after the first run. A code block can set its own on its fence line.

- Type: `string`
- Default: `'Run again'`

### `runnable.button`

Where the button goes: under the block, in the title bar, or both. A code block can set its own on its fence line.

- Type: `'below' | 'title' | 'both'`
- Default: `'below'`

### `runnable.outputDelay`

Milliseconds between the lines of scripted output (`runnable.output`), so that it prints as if the code ran. `0` prints it all at once. A code block can set its own on its fence line.

- Type: `number`
- Default: `200`

A runtime module is a package path, or a path from the project root. This site gives runs 5 seconds, and a site with a Ruby runtime adds it to `runtimes`:

```js title="astro.config.mjs"
codeblocks({
  runnable: {
    runtimes: { ruby: './src/runtimes/ruby.ts' },
    timeout: 5000,
  },
});
```

The guide to [adding a runtime](https://ewels.github.io/starlight-codeblocks/extend/add-a-runtime/) shows how to write one.

- [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block.

## Limitations

- The code must be complete to run. Put the imports and set-up in [hidden lines](https://ewels.github.io/starlight-codeblocks/features/hidden-lines/) if they distract from the example.
- The Python runtime has no standard input, so `input()` fails.
- Python packages from PyPI must be pure Python. A package with compiled code works only if it is part of Pyodide.
- JavaScript and TypeScript code cannot `import` modules, and has no access to the page.
- TypeScript blocks with JSX (`tsx`) do not run.
- The Python runtime loads Pyodide from the jsDelivr CDN, and packages from PyPI. Every runtime runs its worker from a `blob:` URL. A Content Security Policy must allow all three.
- To stop a Python run, the runtime ends its worker. The next run downloads the runtime again, from the browser cache if the browser kept it.

## Related

- [Open in playground](https://ewels.github.io/starlight-codeblocks/features/open-in-playground/): send the code to an online playground, for languages that cannot run in the browser.
- [Hidden lines](https://ewels.github.io/starlight-codeblocks/features/hidden-lines/): keep the code complete for the **Run code** button, while readers see only the part you explain.
- [Add a runtime](https://ewels.github.io/starlight-codeblocks/extend/add-a-runtime/): run another language, with the interface that the built-in runtimes use.
