A terminal emulator for the web.
wterm ("dub-term") renders to the DOM — native text selection, copy/paste, find, and accessibility work directly on the mounted rows. The core is written in Zig and compiled to WASM for near-native performance.
| Package | Description |
|---|---|
@wterm/core |
Headless WASM bridge, TerminalCore interface, WebSocket transport |
@wterm/dom |
DOM renderer, input handler — vanilla JS terminal |
@wterm/react |
React component + useTerminal hook (TypeScript) |
@wterm/vue |
Vue 3 component + template ref API |
@wterm/ghostty |
Full-featured VT emulation core powered by libghostty |
@wterm/just-bash |
In-browser Bash shell powered by just-bash |
@wterm/markdown |
Render Markdown in the terminal |
- Pluggable cores — built-in lightweight Zig core (~12 KB) or opt-in libghostty backend (~400 KB) for full VT compliance
- Zig + WASM core — VT100/VT220/xterm escape sequence parser compiled to a ~12 KB
.wasmbinary (release build) - DOM rendering — native text selection, clipboard, browser find, and screen reader support for mounted rows
- Native hyperlinks — OSC 8 links remain attached to their exact cells through viewport and scrollback, with safe HTTP(S) anchors
- Dirty-row tracking — only touched rows are re-rendered each frame via
requestAnimationFrame - Frame-direct scheduling — writes queue their render on the next animation frame without an extra timer hop
- Synchronized output — mode 2026 blocks paint atomically with a bounded recovery deadline
- Themes — CSS custom properties with built-in Default, Solarized Dark, Monokai, and Light themes
- Alternate screen buffer —
vim,less,htop, and similar apps work correctly - Windowed scrollback history — configurable ring buffer with a bounded visible DOM window
- Wide Unicode cells — CJK, fullwidth, and emoji codepoints keep cursor-addressed redraws aligned
- Grapheme strings — the Ghostty core preserves combining marks and ZWJ emoji through the DOM renderer and scrollback
- Kitty terminal images — Ghostty-backed terminals render direct PNG/RGB/RGBA graphics in a scroll-aware canvas overlay with configurable display bounds; implicit image placements keep following prompts visually below the image
- 24-bit color — full RGB SGR support
- Auto-resize —
ResizeObserver-based terminal resizing - WebSocket transport — connect to a PTY backend with binary framing and reconnection
- Mouse and focus reporting — DOM input for SGR mouse tracking and terminal focus events
- Kitty keyboard protocol: negotiated key disambiguation, event types, alternate keys, all-key reporting, and associated text
pnpm installzig buildFor a release build:
zig build -Doptimize=ReleaseSmallThe built binary is committed at packages/@wterm/core/wasm/wterm.wasm and CI fails if it does not match the Zig sources, so rebuild and commit it with any change under src/.
src/unicode_width_table.zig holds the East Asian Width ranges the core uses to decide cell width. It is generated, not hand-edited. Run this when Unicode publishes a new version, after bumping UNICODE_VERSION in the script:
node scripts/gen-unicode-width.mjspnpm buildServe the web/ directory with any static file server:
cd web && python3 -m http.server 8000For Kitty image support, use the Ghostty example instead. It loads
@wterm/ghostty, which provides the graphics-capable core; the built-in core
consumes unsupported Kitty APC payloads safely but does not decode images.
pnpm --filter ghostty-example devAll terminal graphics are transient browser/WASM memory. Direct media is
accepted only within the Ghostty image budget (32 MiB by default), with a
32 MiB hard cap per image and 4,096 resident image/placement records per
screen; the DOM overlay separately caps visible canvas backing stores at 32 MiB
and bounds each canvas to the terminal pixel area. Set imageStorageLimit: 0
to disable Ghostty graphics. File paths, shared memory, URLs, Sixel,
iTerm2, animation, and virtual placements are not loaded.
Kitty graphics clients can use the Ghostty example's browser terminal
directly; WTerm reports the viewport and cell pixel sizes required by
commands such as kitten icat --transfer-mode=stream image.png.
The local shell example also forwards the browser viewport dimensions to its
PTY, which lets kitten icat --detect-support work from that embedded shell.
All dev servers use portless to avoid hardcoded ports. Each app is served at a .localhost URL (e.g. nextjs-example.wterm.localhost).
cp web/wterm.wasm examples/nextjs/public/
pnpm --filter nextjs devzig build testApache-2.0