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.
Readers see
from statistics import mean, stdev
reads = [1520, 1610, 1480, 1575]print(f"mean {mean(reads):.0f}, sd {stdev(reads):.1f}")You write
```py runnablefrom 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
Section titled “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
Section titled “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, 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 removes the types. |
- For other languages, a site adds a runtime in the
runtimesoption. The guide to adding 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.
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
Section titled “Python packages”The Python runtime reads the import lines of the code before it runs it, and installs the packages they need:
- 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 has them all.
- Other imports install from PyPI with micropip, 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
Section titled “Examples”JavaScript, with standard error
Section titled “JavaScript, with standard error”console.log() goes to standard output and console.error() to standard error:
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.');```js runnableconst 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
Section titled “TypeScript”A TypeScript block runs after the runtime removes the types:
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`);```ts runnableinterface 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
Section titled “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:
from tabulate import tabulate
print(tabulate([["S1", 1520], ["S2", 1610]], headers=["Sample", "Reads"]))```py runnablefrom tabulate import tabulate
print(tabulate([["S1", 1520], ["S2", 1610]], headers=["Sample", "Reads"]))```A package with another name
Section titled “A package with another name”The import is slugify, but the package is python-slugify, so runnable.packages names it:
from slugify import slugify
print(slugify("Hello, World: Run Code!"))```py runnable runnable.packages="python-slugify"from slugify import slugify
print(slugify("Hello, World: Run Code!"))```An error
Section titled “An error”An error in Python shows the traceback, from the line of the example that failed:
counts = {"passed": 3, "failed": 0}print(counts["passed"] / counts["failed"])```py runnablecounts = {"passed": 3, "failed": 0}print(counts["passed"] / counts["failed"])```The timeout
Section titled “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:
Scripted output
Section titled “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:
nextflow run hello.nf```shnextflow run hello.nf# [!output]N E X T F L O W ~ version 25.04.0Launching hello.nf [nice_curie] DSL2executor > local (3) # [!wait 1500][4e/8a1f2c] sayHello (3) | 3 of 3 ✔Hello world!Bonjour le monde!Hola mundo!```Output only
Section titled “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:
```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
Section titled “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:
print(sorted(["chr2", "chr10", "chr1"], key=lambda c: int(c[3:])))```py runnable runnable.button="title"print(sorted(["chr2", "chr10", "chr1"], key=lambda c: int(c[3:])))```Behaviour
Section titled “Behaviour”- Running code:
- The button is under the block, in the style of the code walkthrough buttons. The
buttonoption 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
labelandagainLabeloptions 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 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
awaitis 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.
- The button is under the block, in the style of the code walkthrough buttons. The
- Scripted output:
- The panel shows “Running…”, then the lines of output, with
outputDelaymilliseconds (200 by default) before each line. With0, 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.
- The panel shows “Running…”, then the lines of output, with
- 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 Enter or Space.
- Print:
- The button does not print.
- Without JavaScript:
- The button is hidden.
Options
Section titled “Options”runnable
Section titled “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
Section titled “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,javascriptandtypescript
runnable.timeout
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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:
codeblocks({ runnable: { runtimes: { ruby: './src/runtimes/ruby.ts' }, timeout: 5000, },});The guide to adding a runtime shows how to write one.
Limitations
Section titled “Limitations”- The code must be complete to run. Put the imports and set-up in 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
importmodules, 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
Section titled “Related”- Open in playground: send the code to an online playground, for languages that cannot run in the browser.
- Hidden lines: keep the code complete for the Run code button, while readers see only the part you explain.
- Add a runtime: run another language, with the interface that the built-in runtimes use.