A high-performance browser terminal and headless VT runtime powered by the official nightly
ghostty-vt.wasm.
The native API is byte-oriented and built around Web Streams. Browser terminals use worker isolation when available, select WebGPU, WebGL2, or Canvas 2D at runtime, and keep application policy outside the renderer.
Ghostty's
tipartifact is a nightly build. Each published package version pins and checksums an exact artifact. Pin this package version in production and test upgrades before deployment.
pnpm add @gespenst/core
Import the stylesheet and give the host an explicit height:
<div id="terminal"></div>
<style>
#terminal {
width: 100%;
height: 32rem;
}
</style>
import { createTerminal } from '@gespenst/core';
import '@gespenst/core/style.css';
const terminal = await createTerminal({
container: document.querySelector<HTMLElement>('#terminal')!,
worker: 'dedicated',
renderer: 'auto',
});
terminal.write('\x1b[1;32mGhostty is ready\x1b[0m\r\n');
terminal.focus();
The default worker policy chooses a dedicated worker when Worker and OffscreenCanvas are
available, with a main-thread fallback. The automatic renderer tries WebGPU, then WebGL2, then
Canvas 2D for cell backgrounds. Browser Canvas 2D shapes text on every renderer path. Releases run
the complete browser suite in Chromium and focused API compatibility tests in Firefox and WebKit;
hardware WebGPU availability is not required because the renderer falls back automatically.
The core accepts bidirectional byte streams and does not impose a server protocol:
const connection = terminal.connect({
readable: ptyOutput, // ReadableStream<Uint8Array>
writable: ptyInput, // WritableStream<Uint8Array>
});
terminal.on('resize', ({ cols, rows }) => {
sendPtyResize({ cols, rows });
});
await connection.closed;
Keep PTY bytes as Uint8Array data from the socket to Ghostty. Use
@gespenst/websocket for a ready-made binary WebSocket transport with resize messages,
backpressure, and optional reconnect behavior.
The terminal fills its host, so the host must have a nonzero width and height. A ResizeObserver
fits the grid to container changes. Call fit() after revealing a terminal created in a hidden tab
or dialog.
The default font stack uses installed system monospace fonts. Custom fonts are flexible and do not come from a predefined list:
await terminal.loadFont({
family: 'JetBrains Mono',
source: 'url(/fonts/jetbrains-mono.woff2)',
descriptors: { weight: '400 700' },
});
await terminal.setFont({
family: 'JetBrains Mono, ui-monospace, monospace',
sizePx: 14,
lineHeight: 1.25,
});
loadFont() installs the face in both the document and rendering worker before the grid is
remeasured.
Themes use portable CSS strings or RGB(A) objects and include xterm-compatible named ANSI,
selection, and cursor-accent colors. Install @gespenst/themes for audited, tree-shakable presets:
import { catppuccinMocha } from '@gespenst/themes/catppuccin-mocha';
await terminal.setTheme(catppuccinMocha); // replaces the current theme
await terminal.updateTheme({ cursor: '#ffffff' }); // patches it
Enable allowTransparency at creation when non-selection colors use alpha. Set
minimumContrastRatio when automatic text contrast is required. Missing extended ANSI colors are
generated once by Ghostty rather than calculated in the cell-rendering loop.
const terminal = await createTerminal({
container,
worker: 'dedicated',
renderer: 'auto',
scrollbackLines: 10_000,
accessibility: 'basic',
minimumContrastRatio: 1,
});
worker: 'shared' when many terminals should share one worker event loop.worker: false for deterministic main-thread execution or unsupported environments.accessibility: 'full' when the product needs a live viewport text mirror.terminal.renderer.backend to see which renderer was actually selected.Terminal escape sequences can request clipboard writes and desktop notifications. The core emits
typed clipboardWrite and notification events but does not perform those browser actions
automatically. Version 0.1.0 denies remote clipboard writes; use clipboardWrite only as an audit
record. Validate session trust, user intent, and browser permission before showing notifications.
For user-initiated text and MIME paste, install @gespenst/clipboard. It keeps clipboard support
disabled until the addon is loaded, supports Kitty mode-5522 paste events, denies remote writes,
and requires an application confirmation hook before unsafe text can bypass normal paste safety.
See the clipboard guide.
Every on() call returns a disposable subscription. Calling terminal.dispose() closes active
connections, disposes addons, removes listeners and loaded fonts, releases rendering resources, and
terminates owned workers.
Use the paged buffer API for scrollbars, search, links, transcript views, and compatibility layers:
const visible = await terminal.readBuffer();
const history = await terminal.readBuffer({
start: 0,
end: visible.state.totalRows,
});
terminal.on('viewportChange', ({ state: { viewportY, totalRows, viewportLength } }) => {
updateScrollbar({ viewportY, totalRows, viewportLength });
});
Rows are read directly from Ghostty's active grid; Gespenst does not run a second VT parser. Ranges
are half-open and clamped. viewportY and the scroll event use an absolute zero-based offset from
the oldest retained row. Page only the rows a feature needs on hot paths.
import { createCoreRuntime } from '@gespenst/core/headless';
const runtime = await createCoreRuntime();
const terminal = runtime.createTerminal({ cols: 100, rows: 30 });
terminal.write('\x1b[34mParsed by Ghostty VT\x1b[0m\r\n');
const text = terminal
.viewport()
.viewportRows.map((row) => row.text)
.join('\n');
console.log(text);
runtime.dispose();
Reuse one CoreRuntime for multiple terminals so they share the compiled Ghostty module. The
headless runtime works without a DOM and exposes resolved cells, dirty rows, cursor state, effects,
selection, snapshots, and input encoding.
writeAsync() when a producer needs a parse and render boundary.readBuffer() ranges over rebuilding all retained scrollback.The workspace benchmark is intended for controlled comparisons, not a universal throughput claim:
pnpm bench:throughput
The core guide covers PTY architecture, every configuration group, fonts and themes, event policy, performance measurement, headless use, xterm.js migration, and troubleshooting.
MIT. The vendored Ghostty WASM is MIT licensed; see THIRD_PARTY_NOTICES.md.