Screenshots
Termless generates screenshots from terminal state. SVG is built in with zero dependencies. PNG output flows through Terminal.screenshot() — an auto-picker that prefers the native canvas pipeline (@napi-rs/canvas + ghostty-web WASM, via @termless/ghostty) and falls back to @resvg/resvg-js on hosts where the native canvas isn't available. There's no Chromium dependency anywhere in the pipeline.
For one-off command-output images, use termless record --screenshot. For reproducible docs, demos, and regression fixtures, write a .tape and render it with termless play. Both paths render from parsed terminal state, so ANSI styling, cursor state, and screen geometry stay inspectable before they become an image.
Basic Usage
import { createTerminal } from "@termless/core"
import { createXtermBackend } from "@termless/xtermjs"
const term = createTerminal({ backend: createXtermBackend(), cols: 80, rows: 24 })
term.feed("\x1b[1;38;2;255;85;85mError:\x1b[0m file not found")
const svg = term.screenshotSvg()
const png = await term.screenshot() // auto-picker: native canvas with resvg fallback
const canvasPng = await term.screenshotCanvasPng() // explicit native canvas (requires @termless/ghostty)
const resvgPng = await term.screenshotPng() // explicit resvg (requires @resvg/resvg-js)Saving to File
import { writeFile } from "node:fs/promises"
// SVG
const svg = term.screenshotSvg()
await writeFile("/tmp/terminal.svg", svg, "utf-8")
// PNG via the auto-picker (preferred for routine use)
const png = await term.screenshot()
await writeFile("/tmp/terminal.png", png)Options
screenshotSvg() accepts an optional SvgScreenshotOptions object:
const svg = term.screenshotSvg({
fontFamily: "'JetBrains Mono', monospace",
fontSize: 16,
cellWidth: 9.6,
cellHeight: 20,
padding: 16,
borderRadius: 8,
windowBar: "colorful",
margin: 24,
marginFill: "#111827",
theme: {
foreground: "#f8f8f2",
background: "#282a36",
cursor: "#f8f8f0",
palette: { 1: "#ff5555", 2: "#50fa7b" },
},
})| Option | Type | Default | Description |
|---|---|---|---|
fontFamily | string | "'Menlo', 'Monaco', 'Courier New', monospace" | CSS font-family for text |
fontSize | number | 16 | Font size in px |
cellWidth | number | 9.6 | Character cell width in px |
cellHeight | number | 20 | Character cell height in px |
theme | SvgTheme | (dark theme) | Color theme |
padding | number | 0 | Padding between terminal cells and the frame |
borderRadius | number | 0 | Radius for the terminal background rectangle |
windowBar | "none" | "rings" | "colorful" | "none" | Optional macOS-style window controls |
windowBarSize | number | 40 | Height reserved for the window bar |
margin | number | 0 | Outer image margin |
marginFill | string | transparent | Fill color behind the terminal frame and margin |
Polished Frames
Use visual frame options when the screenshot is meant for docs, blog posts, or release notes:
const png = await term.screenshotPng({
theme: { background: "#111827", foreground: "#f9fafb" },
padding: 18,
borderRadius: 8,
windowBar: "colorful",
margin: 24,
marginFill: "#0b1020",
})The same options are available from .tape settings and termless play flags:
Set Theme "github-dark"
Set Padding 18
Set BorderRadius 8
Set WindowBar "colorful"
Set Margin 24
Set MarginFill "#0b1020"
Type "bun test"
Enter
Sleep 1s
Screenshot build.pngtermless play demo.tape -o demo.png --window-bar colorful --padding 18 --margin 24 --margin-fill '#0b1020'Command Output Captures
For a single command, termless record --screenshot is the shortest path:
termless record --cols 100 --rows 30 --screenshot /tmp/listing.png -- ls -laThis runs the command in a PTY, waits for content, captures the terminal state, and writes SVG or PNG based on the filename extension. Add --keys and --wait-for for simple interactive captures:
termless record --wait-for "ready>" --keys j,j,Enter --screenshot /tmp/app.svg -- bun km view /path/to/vaultUse .tape when the capture needs setup, timing, hidden commands, multiple screenshots, or a future GIF/APNG/SVG recording.
SvgTheme
| Field | Type | Default | Description |
|---|---|---|---|
foreground | string | "#d4d4d4" | Default text color |
background | string | "#1e1e1e" | Background color |
cursor | string | "#aeafad" | Cursor color |
palette | Record<number, string> | -- | Override ANSI palette colors (0-255) |
PNG Options
screenshotPng() accepts a PngScreenshotOptions object (extends SvgScreenshotOptions with an additional scale option):
const png = await term.screenshotPng({
scale: 2, // default: 2 (retina-quality)
theme: { background: "#282a36" },
})| Option | Type | Default | Description |
|---|---|---|---|
scale | number | 2 | Render scale factor (1 = actual size) |
All SvgScreenshotOptions (fontFamily, fontSize, cellWidth, cellHeight, theme) are also accepted.
Native Canvas Options
screenshot() (auto-picker) and screenshotCanvasPng() accept a ScreenshotOptions object. When the native canvas path is selected (the default whenever @termless/ghostty is installed), bytes go through ghostty-web's CanvasRenderer + @napi-rs/canvas — same renderer ghostty.org's terminal uses, just driven by JS.
Install @termless/ghostty to enable the native canvas path:
bun add -d @termless/ghosttyconst png = await term.screenshot({
fontSize: 14,
fontFamily: "'Fira Code', monospace",
fontPath: "/path/to/iosevka.ttf",
theme: { background: "#1a1b26", foreground: "#c0caf5" },
dpr: 2,
})Use the auto-picker for routine work. Use screenshotCanvasPng() when you specifically need the native canvas path and don't want a silent fallback to resvg (e.g., visual-regression tests that pin the ghostty renderer's output).
| Option | Type | Default | Description |
|---|---|---|---|
cols | number | backend cols | Override grid width |
rows | number | backend rows | Override grid height |
fontSize | number | 16 | Glyph size in CSS px |
fontFamily | string | "monospace" | CSS font family |
fontPath | string | -- | Path to .ttf/.otf bundled as the first family |
dpr | number | 2 | Device pixel ratio |
theme | CanvasTheme | Tokyo Night Storm | Color palette |
Standalone Functions
You can also use the screenshot functions directly on any TerminalReadable:
import { screenshotSvg, screenshotPng } from "@termless/core"
import { renderTerminalPng } from "@termless/ghostty"
const svg = screenshotSvg(term, { theme: { background: "#000" } })
const resvgPng = await screenshotPng(term, { scale: 3 }) // explicit resvg path
const canvasPng = await renderTerminalPng(term, { fontSize: 14 }) // explicit native canvas pathWhat Gets Rendered
- Text content with proper monospace positioning
- Foreground colors (ANSI 16, 256-color, truecolor)
- Background colors (merged into rectangles for adjacent same-color cells)
- Bold (via
font-weight="bold") - Italic (via
font-style="italic") - Faint/dim (via
opacity="0.5") - Underline (via
text-decoration="underline") - Strikethrough (via
text-decoration="line-through") - Inverse video (fg/bg swapped)
- Wide characters (double-width CJK, emoji)
- Cursor (block, underline, or beam -- with semi-transparent overlay)