The terminal core,
rewritten in Rust.
A from-scratch VT100/xterm engine compiled to WebAssembly, with Canvas2D and WebGL renderers built in. Faster parsing, a smaller bundle, and an untrusted-byte parser that is memory-safe by construction.
Build the module with
./build.sh and serve over HTTP, or
open the hosted live demo.
A real terminal, not a textarea
Everything a browser terminal needs, built in — no addon zoo to assemble.
From-scratch parser
DEC (Williams) state machine: CSI/OSC/DCS, SGR with 256-color and 24-bit true color, scroll regions, alternate screen, DECSET modes, host replies.
Grapheme-aware
Astral emoji, wide CJK cells, and cluster merging — combining accents, ZWJ sequences, variation selectors and flags collapse into one cell.
Two renderers
Canvas2D and an instanced WebGL renderer that re-uploads only changed rows. A one-row edit repaints a fraction of a full frame.
Sixel · iTerm2 · Kitty
Sixel decoded in the core; iTerm2 (OSC 1337) and Kitty graphics (APC) images decoded natively by the browser — so no image codec bloats the WASM.
Hyperlinks & palette
OSC 8 links plus automatic URL detection, and a live dynamic palette (OSC 4/10/11/12) with color-query replies.
Safe at the boundary
Every untrusted byte is parsed in memory-safe Rust with bounded buffers; the JS layer never interprets an escape sequence itself.
The same core, on the GPU
The engine also drives a native desktop terminal — winit + wgpu (Metal / Vulkan / DX12), no webview, running the identical Rust parser, grid and key encoding as the web build.
Instanced wgpu
A GPU cell renderer over a fontdue glyph atlas: 256-color and true color, wide/CJK cells, bold/italic (real font faces or synthetic), underline and strikethrough.
Inline graphics
Sixel and Kitty pixels drawn directly; iTerm2 and encoded Kitty images
(PNG/JPEG/GIF/BMP/WebP) decoded through the image crate and
composited over the cells.
Selection & scrollback
Click-drag, shift-click, double-click word and triple-click line selection that spans scrollback and auto-scrolls at the edges; select-all, clipboard copy/paste, OSC 8 links, and a blinking cursor.
The numbers
Reproducible head-to-head against xterm.js in the same browser on identical payloads.
Full methodology and the xterm.js comparison table in COMPARISON.md. FPS is software-GL (SwiftShader) here — a lower bound; real GPUs are faster.
Wire a PTY in a few lines
An ES module with TypeScript types. Point it at any byte stream.
# npm — ships wasm + JS, both renderers npm install ferroterm # or build from source git clone https://github.com/DatanoiseTV/ferroterm cd ferroterm && ./build.sh
import { Ferroterm } from 'ferroterm'; const term = await Ferroterm.create(el, { cols: 80, rows: 24, renderer: 'webgl', }); // keystrokes → PTY, PTY bytes → screen const ws = new WebSocket('wss://host/pty'); ws.binaryType = 'arraybuffer'; term.onData(b => ws.send(b)); ws.onmessage = e => term.write(new Uint8Array(e.data));
// register the <ferro-term> tag once import { defineFerroTermElement } from 'ferroterm'; defineFerroTermElement(); <!-- then use it declaratively in HTML --> <ferro-term cols="80" rows="24" renderer="webgl"></ferro-term> const el = document.querySelector('ferro-term'); await el.ready; // WASM + view are up el.addEventListener('data', e => ws.send(e.detail)); ws.onmessage = m => el.write(new Uint8Array(m.data));
Demos & examples
Everything runs client-side — no backend.
Interactive shell →
A local demo shell with loadtest, colors,
chars, links and a live benchmark command.
Renderer benchmark →
Detects your GPU, then times render, per-frame pipeline and incremental repaints. Tables plus copyable JSON.
Web Serial →
Talk to a real serial device (microcontroller, modem) straight from the browser through ferroterm.