Ghostty's terminal, in the browser.
The real libghostty-vt core, unpatched, compiled to WebAssembly. Four renderers that draw only what changed, built to run dozens of terminals on one page.
npm install ghostty-webgpu
Runs every terminal in Fregat, an IDE in your browser.
Measured
Like for like: each renderer against the same renderer in xterm.js. Losses stay on the chart.
CPU energy, WebGL against xterm.js WebGL
× CPU energy, lower is better- Heavy log output
- Heavy Unicode output
- One Unicode line per tick Loss: about 32% more CPU energy.
- One ASCII line per tick Loss: about 36% more CPU energy.
- Typing-like edits Roughly even.
| Workload | History | CPU energy | Instructions |
|---|---|---|---|
| Heavy log output | 8,841 final rows | 0.751 | 0.690 |
| Heavy Unicode output | 8,841 final rows | 0.730 | 0.689 |
| One Unicode line per tick | Equal by construction | 1.320 | 1.348 |
| One ASCII line per tick | Equal by construction | 1.361 | 1.417 |
| Typing-like edits | Equal by construction | 1.010 | 1.048 |
CPU energy, DOM against xterm.js DOM
× CPU energy, lower is better- Heavy log output
- One Unicode line per tick
- One ASCII line per tick
- Typing-like edits Loss: about 15% more CPU energy.
| Workload | History | CPU energy | Instructions |
|---|---|---|---|
| Heavy log output | Full stream | 0.375 | 0.445 |
| One Unicode line per tick | Equal by construction | 0.392 | 0.497 |
| One ASCII line per tick | Equal by construction | 0.735 | 0.839 |
| Typing-like edits | Equal by construction | 1.147 | 1.151 |
Parse throughput
Write and keystroke to screen
import { Terminal } from 'ghostty-webgpu'
const host = document.getElementById('terminal')!
const terminal = await Terminal.create()
await terminal.open(host)
terminal.write('hello from ghostty\r\n')
terminal.focus()
Connect a WebSocket PTY
const socket = new WebSocket('wss://host/pty')
socket.binaryType = 'arraybuffer'
terminal.onData((bytes) => {
if (socket.readyState !== WebSocket.OPEN) return
socket.send(new Uint8Array(bytes))
})
socket.addEventListener('message', ({ data }) => {
if (!(data instanceof ArrayBuffer)) return
terminal.write(new Uint8Array(data))
})
The mount needs a height and width. Dispose the terminal and close the socket when the connection ends.
Only what changed is drawn. The renderer tracks damage per cell, so an idle terminal costs nothing and a busy one costs only its busy rows.
Runs anywhere. WebGPU where the browser has it, then WebGL2, then Canvas2D, then plain DOM. Each draws the same cells. This page is drawing with html.
Off the main thread. Import ghostty-webgpu/worker and the emulator,
frame builder and renderer run in a worker on an OffscreenCanvas.
Correct because it is Ghostty
Parsing, modes, wrapping, reflow, selection text and Unicode widths come from Ghostty's own core. The renderer and input path are ours, and tested in real browsers.
Unpatched and reproducible. libghostty-vt at revision 7b11f3d, built with Zig 0.16.0, recorded with "patched": false. Anyone can rebuild the same wasm byte for byte.
Modern protocols, built in. Kitty keyboard with press, repeat and release. Grapheme clusters (mode 2027). OSC 8 links. Focus, bracketed paste and color-scheme reports. Programs in the terminal turn them on as they need them, with no addons to install.
Clipboard behind a policy. OSC 52 writes are denied until your app allows them, and clipboard reads are never answered.
Checked against the others. In 169 cases (132 from esctest2, 37 Unicode and delivery cases), ghostty-webgpu 0.3.20 passed 167, xterm.js 6.0.0 passed 126 and ghostty-web 0.4.0 passed 161. Headless Chromium 153, 8 Oct 2026. Every case and failure.
Not there yet: images (Sixel, Kitty graphics), ligatures, search, synchronized output. Still failing in esctest2, inherited from Ghostty's core: test_CHA_RespectsOriginMode and test_TBC_Default.
Coming from xterm.js
Fit, WebGL and Unicode graphemes are built in. Creation is async, and data stays as bytes from the PTY to the screen.
Read the migration guide| xterm.js | ghostty-webgpu |
|---|---|
| new Terminal(opts) | await Terminal.create(opts) |
| term.onData(str => …) | terminal.onData(bytes => …) |
| loadAddon(new FitAddon()) | built in |
| loadAddon(new WebglAddon()) | built in, plus WebGPU |
| @xterm/headless | GhosttyRuntime.create() |