wterm Review: A DOM-Rendering Web Terminal With a Zig/WASM Core
A terminal emulator for the web
At a glance
- What is it?
- wterm is a Vercel Labs terminal emulator for the browser that renders to the DOM, ships a ~12 KB Zig core compiled to WASM, and offers React, Vue, and Svelte bindings. It is a good fit for dashboards and docs demos, and the wrong tool if you need production PTY plumbing out of the box.
- Who is it for?
- Adopt wterm if you are embedding a terminal into a web UI and want native text selection, browser find, and screen reader support without building a renderer. Skip it if you expect a ready-made SSH or PTY service: the WebSocket transport exists, but the backend is yours to write, and the SSH example is only a starting point.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What wterm Solves, and Who It Is For
Most browser terminals are canvas renderers. Text is painted as pixels, which means the browser's own text selection, copy, find, and screen reader support do not work on the terminal contents. wterm takes the other route: it renders to the DOM, so the mounted rows are real text nodes and those browser features apply directly. That single design decision is the project's identity.
The audience is web developers embedding a terminal into a product surface: an admin dashboard with a shell, a documentation site with a live demo, a hosted IDE, or a support tool where an operator watches output. The README describes wterm as "a terminal emulator for the web" and the repository is organized as a pnpm monorepo with separate packages for the core, the DOM renderer, and framework bindings, which tells you the intended consumption model is a library, not an application you deploy whole.
It is a Vercel Labs experiment. The README's badge labels it as such, and that label is a fair description of the maturity level: the packages are published to npm, the API surface is documented, but the project is not positioned as infrastructure with a support contract.
How the Zig Core, WASM Bridge, and DOM Renderer Fit Together
The architecture has three layers. At the bottom is a VT parser written in Zig and compiled to WebAssembly. The README states the release build is a roughly 12 KB .wasm binary that handles VT100, VT220, and xterm escape sequences. Above it sits @wterm/core, described as a headless WASM bridge exposing a TerminalCore interface plus a WebSocket transport. On top is @wterm/dom, the renderer and input handler, with framework wrappers for React, Vue 3, and Svelte.
Data flows in one direction for input and the other for output. Bytes from a PTY arrive over the WebSocket transport, the core parses them into a grid state, and the DOM renderer updates only the rows marked dirty. The README calls this dirty-row tracking and says touched rows are re-rendered each frame via requestAnimationFrame. Writes queue their render on the next animation frame rather than going through an extra timer hop, which the README names frame-direct scheduling.
The core is pluggable. The default Zig parser is small; @wterm/ghostty is an opt-in backend powered by libghostty at roughly 400 KB that the README describes as full-featured VT emulation. The size gap is the trade-off. If you need grapheme clusters preserved through combining marks and ZWJ emoji, or Kitty terminal image rendering in a scroll-aware canvas overlay, that is the Ghostty path. If you need a small parser for ordinary shell output, the default core is the cheaper choice.
Two details are worth noting because they affect correctness rather than looks. The README states that OSC 8 hyperlinks stay attached to their exact cells through viewport and scrollback, with safe HTTP(S) anchors, and that wide Unicode cells (CJK, fullwidth, emoji) keep cursor-addressed redraws aligned. Both are the kind of thing that breaks silently in a hand-rolled renderer.
Installing wterm and Rendering a First Terminal
The published entry point is the npm package @wterm/core. The README shows the npm version badge for that package and lists the sibling packages in a table; the repository's example directories include examples/vite, examples/nextjs, examples/vue, and examples/svelte, which are the fastest places to see a working wiring.
Install the core package with your package manager:
pnpm add @wterm/coreThe repository itself targets Node.js 24+ and pnpm 11+, per the engines field in the root package.json and the prerequisites section of the README. If you are building from source rather than consuming the published package, clone the repository and install the workspace:
pnpm installBuilding the WASM core from source requires Zig 0.16.0 or newer. The README gives two forms, a default build and a release build:
zig build
zig build -Doptimize=ReleaseSmallOne constraint matters here. The README states that the built binary is committed at packages/@wterm/core/wasm/wterm.wasm and that CI fails if it does not match the Zig sources, so any change under src/ requires a rebuild and a commit of that file. If you fork the parser, you inherit that obligation.
For a framework integration, the README points at the package table: @wterm/react provides a React component plus a useTerminal hook, @wterm/vue provides a Vue 3 component with a template ref API, and @wterm/svelte provides a Svelte component with a callback API. The documentation site documents routes at /get-started, /react, and /api-reference, so the exact prop and option names live there rather than in the README.
Where wterm Stops: Transport, Backends, and the Missing Pieces
wterm is a terminal emulator, not a terminal server. The README lists a WebSocket transport with binary framing and reconnection, and the examples directory contains examples/ssh, but nothing in the README describes a hosted PTY service, an authentication layer, or a session manager. You supply the backend that owns the shell process and speaks to the browser over the socket. That is a substantial amount of work, and it is the most common place where an evaluation of wterm turns into an evaluation of your own infrastructure.
The DOM renderer has a cost the canvas approach avoids. Every visible row is a set of DOM nodes. The README addresses this with windowed scrollback: a configurable ring buffer for history plus a bounded visible DOM window. That bounds the node count, but it also means very large scrollback settings interact with DOM performance in a way that a canvas renderer would not experience. If your use case is a firehose of output at high frame rates, benchmark that configuration before committing.
The package is also explicitly experimental. Vercel Labs experiments do not carry the same compatibility expectations as a stable product, and the README does not document a deprecation policy or a rollback path for the WASM binary. For a docs demo that is fine. For a customer-facing shell, treat the version pin as part of your release process.
wterm vs xterm.js: DOM Rows Against a Canvas Grid
The obvious comparison is xterm.js, the long-standing browser terminal that most web terminal projects build on. The difference is the rendering target. xterm.js draws to a canvas, which gives it predictable paint cost independent of how much text is on screen, and it is the reason it has been the default choice for years. wterm draws to the DOM, which gives it native selection, clipboard, browser find, and screen reader support on the mounted rows, as the README states.
That is a real trade, not a marketing line. If your users need to select text with the mouse and have the browser's own find bar locate output, the DOM route removes code you would otherwise write. If your terminal is a high-throughput log viewer where the grid changes constantly, canvas is the safer default and wterm's windowed scrollback becomes a tuning problem.
There is a second axis: the parser. xterm.js ships a JavaScript parser. wterm's default core is Zig compiled to WASM, and the optional @wterm/ghostty package uses libghostty for full VT compliance. The README frames the choice as roughly 12 KB versus roughly 400 KB. If you need the Ghostty feature set (grapheme strings, Kitty images), wterm offers something xterm.js does not bundle. If you need the smallest possible payload, the default core is the argument.
A third difference is packaging. wterm ships first-party React, Vue 3, and Svelte components. With xterm.js you typically add a community wrapper. That matters if you want a component and a hook rather than an imperative API.
Maintenance, Licence, and Upgrade Cost
The repository is not archived, and the last push was on 2026-09-22, one day before this writing. Recent releases are v0.4.0 and v0.4.1 on 2026-09-02 and v0.5.0 on 2026-09-04, so the version line is still moving in the 0.x range. That is a signal about API stability: pre-1.0 releases can change shape, and the README does not promise otherwise.
The licence is Apache-2.0, stated in the README badge and in the LICENSE file, and the root package.json carries the same identifier. Apache-2.0 includes an express patent grant and requires that you preserve notices and state changes. It is permissive, so embedding wterm in a commercial product is compatible with the licence terms. This is a description of the licence, not legal advice; if you are shipping a modified WASM binary, have counsel review your notice obligations.
Upgrade cost has one project-specific wrinkle. Because the compiled WASM binary is committed to the repository and CI fails when it does not match src/, upgrading the parser is not just an npm version bump if you build from source: it is a rebuild plus a commit. The README also documents a generated Unicode width table at src/unicode_width_table.zig, regenerated with node scripts/gen-unicode-width.mjs after bumping UNICODE_VERSION. When Unicode publishes a new version, that regeneration is part of your upgrade path.
Editorial conclusion
Adopt wterm if you are embedding a terminal into a web UI and want native text selection, browser find, and screen reader support without building a renderer. Skip it if you expect a ready-made SSH or PTY service: the WebSocket transport exists, but the backend is yours to write, and the SSH example is only a starting point. Before committing, check which core you need (@wterm/core at ~12 KB versus @wterm/ghostty at ~400 KB), confirm the WASM binary in packages/@wterm/core/wasm/wterm.wasm matches the Zig sources you build, and verify that your deployment target can serve a .wasm file.
Frequently asked questions
What does a terminal emulator like wterm actually do?
It interprets VT100, VT220, and xterm escape sequences and turns them into a rendered grid of cells. In wterm's case the parser is written in Zig, compiled to WASM, and the resulting state is rendered into DOM rows by @wterm/dom.
What is the difference between xterm and wterm?
The rendering target differs: xterm.js paints to a canvas, while wterm renders to the DOM so native text selection, clipboard, browser find, and screen reader support work on the mounted rows. wterm also offers a pluggable core, with a lightweight Zig parser or an opt-in libghostty backend.
What are the alternatives to wterm for a browser terminal?
xterm.js is the closest alternative and the one most web terminal projects build on, with a canvas renderer and a JavaScript parser. The practical difference is that wterm ships first-party React, Vue 3, and Svelte bindings, while xterm.js integrations usually rely on a community wrapper.
What does "web terminal" mean in the context of wterm?
It means a terminal emulator that runs in the browser, with the shell process living elsewhere and bytes arriving over a transport. wterm provides a WebSocket transport with binary framing and reconnection, but the README does not describe a hosted PTY backend, so that side is left to you.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/vercel-labs-wterm)
Community notes