Twoslash: Code Blocks with a Heartbeat

Code is poetry, but compiling it is reality.

Most technical writing treats code as a still life. Syntax highlighting makes it beautiful, but underneath, it remains dead text. No type checking. No language server. No context.

It looks perfect on the screen, but the moment a reader copies it into their editor, the illusion shatters. Missing imports. Type mismatches. Phantom environments. The real world of code is interconnected; a static markdown block is an agreed-upon lie.

We shouldn't just format code. We should give it a heartbeat. Enter Twoslash.

Here’s how we make it live.

  1. Make It Honest
  2. Make It Conversational
  3. Make It Anticipate
  4. Make It Connected
  5. Make It Pure
  6. Make It Cinematic

#1. Make It Honest

A good editor doesn't just display text; it interrogates it. When code is wrong, it should bleed red. By running a real TypeScript compiler under the hood, we capture actual diagnostics. No more writing fake error comments. The code block feels its own fractures.

// Type 'string' is not assignable to type 'number'.
const 
Type 'string' is not assignable to type 'number'.
hello
: number = "world"
Sidenote: Type errors are captured directly from the compiler

#2. Make It Conversational

Reading code is often a monologue. Twoslash makes it a dialogue. By using // ^?, you can ask the compiler what it thinks. It bridges the gap between human intent and machine inference, revealing the hidden geometry of your data.

/**
 * Greets a person.
 */
function function greet(name: string): string
Greets a person.
greet
(name: stringname: string) {
return `Hello ${name: stringname}` }
function greet(name: string): string
Greets a person.
greet
Sidenote: Hovering over a variable reveals its computed signature

#3. Make It Anticipate

The most intimate moment between a developer and their editor is the autocomplete dropdown. It is the machine finishing your sentence. We can bring this exact IntelliSense experience into static markdown using // ^|.

const const greeting: "world"greeting = "world"

greeting
  • greeting
.
Identifier expected.
Sidenote: Triggering the editor's autocomplete suggestions

#4. Make It Connected

Code never exists in a vacuum. It imports, exports, and depends on a constellation of other files. Traditional snippets force you to hallucinate the file system. Twoslash spins up a virtual one. You can define multiple files in a single block, and the compiler will resolve the imports perfectly.

// @filename: utils.ts
export const const double: (n: number) => numberdouble = (n: numbern: number) => n: numbern * 2

// @filename: index.ts
import { const double: (n: number) => numberdouble } from "./utils"

function double(n: number): numberdouble(2)
const double: (n: number) => number
double
Sidenote: Creating a virtual file system for cross-file imports

#5. Make It Pure

The stage needs a backstage. Often, to make a snippet compile, you need lines of tedious boilerplate, mocked data, and interface definitions. But the reader only cares about the core logic. ---cut--- acts as the curtain. The compiler processes the noise; the reader experiences only the signal.

const const shown: numbershown = const secret: 32secret + 1
Sidenote: Hiding the setup code from the final render

#6. Make It Cinematic

Sometimes you need to direct the reader's eye, like a camera lens pulling focus. Whether it's a specific line of code or a recurring variable, granular highlighting cuts through the visual noise and whispers: Look here.

const const a: 1a = 1
const const b: 2b = 2
const const c: numberc = const a: 1a + const b: 2b
Sidenote: Line-level highlighting for precise focus
const const hello: "world"hello = "world"
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const hello: "world"hello)
Sidenote: Regex highlighting to track a specific token

These aren't just styling tricks. They are guarantees. When your documentation is backed by a real compiler, it can never lie to your readers. It survives refactoring. It outlives API changes.

We are no longer writing static documentation. We are shipping tiny, frozen moments of a living editor.