Skip to content

Live code on a slide

In this guide you add editors to a deck: one that shows code, one that runs it with the output on the slide, one that imports a package from npm, a Python one, a diff, and one whose edits are saved to a file in your project. It takes about ten minutes.

You need a deck (see Install) running with npm run devpnpm devyarn devbun run dev. Every block below is a fenced code block in deck.mdx; the word in braces after the language turns it into an editor. The editor is Monaco, the editor VS Code is built on. It ships with the engine and loads only when a slide with an editor is shown, so a deck without one never downloads it.

Add a slide with a {monaco} fence:

---
## An editor
```ts {monaco}{height:'auto'}
const tide = { height: 6.1, at: "06:12" };
```

Click into it and type: completions, hovers and errors work as they do in VS Code. While the editor has focus, your keys go to it; press Esc to hand them back to the deck.

The second braces hold options: height (any CSS length, or 'auto' to grow with the code), lines (line numbers), readonly, and editorOptions (Monaco’s own settings).

Change {monaco} to {monaco-run}:

```ts {monaco-run}
const heights = [5.2, 6.1, 6.4, 5.9];
const highest = Math.max(...heights);
console.log(`High water: ${highest} m`);
```

The output appears under the editor. The block runs when its slide arrives, and again when you pause typing for two-thirds of a second; Ctrl/⌘ Enter or the Run button runs it on demand. {autorun:false} waits for the button. Reset puts the code back as written in the deck, and Copy copies it.

JavaScript and TypeScript run in a Web Worker, away from the deck. The console is captured, an error keeps its line number, await works at the top level, and a run that takes longer than five seconds is stopped, so an endless loop can’t freeze the slide. {timeout:10000} gives it longer.

To ask the room what the code prints before showing it, hold the output back until the next click:

```ts {monaco-run}{showOutputAt:'+1', outputPosition:'right'}
console.log([1, 2, 3].map((n) => n * 2));
```

showOutputAt is a click step like <Click at>: '+1' is the next click, a number is an absolute step. outputPosition puts the output to the 'right' of the editor or at the 'bottom' (the default), and outputHeight caps its height ('200px').

A bare import loads from esm.sh when the block runs:

```ts {monaco-run}
import { chunk } from "lodash-es";
console.log(chunk(["a", "b", "c", "d", "e"], 2));
```

The editor also fetches the package’s type declarations, so chunk has its real signature on hover. To pin the version the block imports, and to load the types before anyone types an import, name the package in the headmatter:

---
title: My talk
monacoPackages:
lodash-es: "4.17.21"
---

monacoTypes says where types come from: auto (the default) bundles the declarations from the deck’s own node_modules into kadal-deck build, and fetches the rest from jsDelivr; local uses only the installed ones, cdn only the CDN, and none turns types off. monacoRunImports points bare imports at another CDN, or false makes them an error. Both need the network at run time unless the package is installed in the deck.

A python fence runs in Pyodide, CPython compiled to WebAssembly:

```python {monaco-run}
import statistics
heights = [5.2, 6.1, 6.4, 5.9]
round(statistics.mean(heights), 2)
```

The value of the last expression is shown, as in a Python prompt, and print output streams in. The first Python run on a page downloads the interpreter (Pyodide 0.28.3, from jsDelivr), so it takes a few seconds; every later run reuses it, and all the Python blocks of a deck share one interpreter. Packages you import (numpy, pandas) load on their own. A Python run may take 20 seconds once the interpreter is up.

{monaco-diff} takes the original, a line of ~~~, and the changed code:

```ts {monaco-diff}
const tide = 6.1;
const safe = tide < 7;
~~~
const tide = 6.4;
const safe = tide < 6.5;
```

It shows the two side by side; {sideBySide:false} shows one column with the changes inline.

7. Edit a file in your project from the slide

Section titled “7. Edit a file in your project from the slide”

{monaco-write} saves what you type back to the source while kadal-deck dev runs. Point it at a real file with a <<< import. Create snippets/tide.ts beside deck.mdx:

snippets/tide.ts
// #region ask
export function isSafe(height: number): boolean {
return height < 7;
}
// #endregion

Then, on a slide:

<<< @/snippets/tide.ts#ask {monaco-write}

@ is the deck’s folder and #ask is the region between // #region ask and // #endregion. Edit the function on the slide: under the editor, the status reads “Saved to snippets/tide.ts” a moment after you stop typing, and the file on disk has your change. A save is refused if the file changed underneath you. Without a <<<, a {monaco-write} fence saves into deck.mdx itself.

In a built deck and on deck.kadal.cc the editor is read-only: only the dev server can write.

Any language can run with a runner of your own: a function from the code to its output. Put runners in setup/code-runners.ts beside deck.mdx. This one pretty-prints JSON:

setup/code-runners.ts
import { defineCodeRunners } from "@kadal/deck";
export default defineCodeRunners({
json(code) {
try {
return { text: JSON.stringify(JSON.parse(code), null, 2) };
} catch (e) {
return { error: (e as Error).message };
}
},
});
```json {monaco-run}
{"tide": 6.4, "safe": false}
```

A runner can return text, HTML (sanitised before it is shown), an element, or { error }, and ctx.write() streams output as it arrives. The second argument, ctx, also carries ctx.signal, aborted when the block runs again or the slide goes away, and ctx.timeout. A runner that calls a server (a SQL database, a compiler) uses fetch with signal: ctx.signal. registerCodeRunner("json", fn) from @kadal/deck does the same from your own code.

  • Print, export and previews show each editor as highlighted code. A {monaco-run} block still runs there, so the PDF page carries its output; a block that imports from the network needs it while exporting.
  • The phone remote and the audience’s phones see the highlighted code, not an editor.
  • On a hosted deck editors and runs work as on your machine; {monaco-write} is read-only.
  • Offline, JavaScript and TypeScript blocks without imports always run. Python, esm.sh imports and CDN types need the network, or a cache from an earlier run on that machine.
  • npx kadal-deck checkpnpm exec kadal-deck checkyarn kadal-deck checkbunx kadal-deck check reports a diff without its ~~~, a run block in a language with no runner, unknown options, and missing snippet files or regions.

Send feedback