Twoslash
Opt a TypeScript or JavaScript code block into Twoslash to add interactive type hovers, inline compiler errors, and `// ^?` type queries — powered by the real TypeScript language service at build time.
Twoslash runs the real TypeScript language service over a code block at build time and
bakes the results into the page: hover any identifier to see its resolved type, surface
expected compiler errors inline, and pin a type with a // ^? query. It’s the same tooling
the TypeScript handbook, VitePress, and Astro Starlight use — rendered with
Shiki + @shikijs/twoslash.
It’s opt-in per block: add twoslash after the language on a ts, tsx, js, or jsx
fence. Every other code block is untouched.
```ts twoslash
const greeting = "Hello, aardvark"
const loud = greeting.toUpperCase()
```
renders, live (hover greeting or loud):
const const greeting: "Hello, aardvark"greeting = "Hello, aardvark"
const const loud: stringloud = const greeting: "Hello, aardvark"greeting.String.toUpperCase(): stringConverts all the alphabetic characters in a string to uppercase.toUpperCase()
Type queries
A // ^? comment, with the ^ under the token you want, pins that token’s type below the
line — handy for showing what an expression infers to:
const const point: {
x: number;
y: number;
}
point = { x: numberx: 10, y: numbery: 20 }
Showing errors
By default Twoslash treats a compiler error as a build problem. To show an error on
purpose, declare its code with // @errors: — the block then renders with the squiggle and
the message inline instead of failing the build:
const count: number = "not a number"
Trimming setup with // ---cut---
Real examples often need setup that distracts from the point. Everything above a
// ---cut--- line is compiled (so types still resolve) but hidden from the rendered block:
const const user: {
id: string;
name: string;
}
user = const db: {
find: (id: string) => {
id: string;
name: string;
};
}
db.find: (id: string) => {
id: string;
name: string;
}
find("u_1")
Multiple files
Use // @filename: to split a block into several modules — imports resolve across them, so
you can demonstrate a small project:
// @filename: shapes.ts
export interface Circle { Circle.kind: "circle"kind: "circle"; Circle.radius: numberradius: number }
// @filename: area.ts
import type { Circle } from "./shapes"
export const const area: (c: Circle) => numberarea = (c: Circlec: Circle) => var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.PI: numberPi. This is the ratio of the circumference of a circle to its diameter.PI * c: Circlec.Circle.radius: numberradius ** 2
Configuration
Twoslash is on by default. Turn it off site-wide, or pick a different
Shiki theme for the rendered blocks, in aardvark.config.yaml:
twoslash:
theme: github-light # Shiki light theme (default: github-light)
themeDark: github-dark # Shiki dark theme (default: github-dark)
timeout: 120 # seconds to allow the render before falling back (default: 120)
Set twoslash: false to disable the feature everywhere, or add twoslash: false to a single
page’s front matter to disable it just there — useful for a page of deliberately incomplete
snippets. Either way, a twoslash-tagged fence falls back to a normal highlighted code block.
Requirements
Twoslash needs Node and three build-time packages — shiki, @shikijs/twoslash, and
typescript — which ship in the scaffolded package.json, so npm install once and
vark build renders your twoslash blocks. If Node or the packages aren’t available (or you
build with --no-bundle), tagged blocks degrade gracefully to plain highlighted code and the
build prints a one-line notice — it never fails the build over a missing toolchain.
Keep twoslash snippets short. So the hover and // ^? popovers can escape the block, a
twoslash block isn’t horizontally scrollable the way a plain code block is — a very long line
extends the page width instead of scrolling. Favor short, focused lines in twoslash blocks.