Build terminal apps with HTML, CSS and DOM.
See the codeBuild terminal apps with HTML, CSS, and DOM.

TermDOM is a JavaScript/TypeScript library that renders HTML and CSS to the terminal. It draws actual DOM nodes to terminal output and redraws the screen when they are mutated, so TUIs and interactive CLIs can be written with vanilla JavaScript, or any frontend web framework or library.
npm install @b9g/termdom
import {TermDOM} from "@b9g/termdom";
const term = new TermDOM();
term.attach();
// The document is a real DOM document.
const {document} = term;
document.body.innerHTML = `
<style>
.card { border: 1px solid #5fafff; padding: 0 1ch; width: 36ch; }
.title { color: #5fafff; font-weight: bold; }
progress { width: 25ch; }
progress::slider-fill { background-color: green; }
.pct { color: #888; }
</style>
<div class="card">
<div class="title">Installing</div>
<div>
<progress id="bar" max="100" value="0"></progress>
<span class="pct" id="pct"></span>
</div>
</div>
`;
// TermDOM observes mutations and re-renders automatically.
let n = 0;
setInterval(() => {
n = (n + 1) % 101;
document.querySelector("progress")!.value = n;
document.getElementById("pct")!.textContent = String(n).padStart(3) + "%";
}, 50);

<style> elements and style attributes cascades
and inherits as in the browser, translated to ANSI color and decoration.document.querySelector(), MutationObserver,
ResizeObserver, and getBoundingClientRect() work and report
cell-based layout.<input>, <textarea>, <select>, checkboxes, radios, and
sliders have terminal-native looks, restylable with CSS; Tab and
:focus work.overflow boxes taller than the terminal
scroll with the wheel, the arrow and page keys, window.scrollTo() and
element.scrollIntoView().customElements.define(), attachShadow(), <slot>,
:host, and scoped styles; the built-in controls are shadow trees.::selection; the caret moves
by grapheme, and text fields bind the readline chords (Ctrl+A/E/K/U/W).Element.requestFullscreen() renders to the alternate
screen; exiting restores the shell and its scrollback.<img> decodes PNG, JPEG, GIF and BMP on the
runtime's web Worker where there is one, and <canvas> has a 2D
context and a "termdom-cellgrid" context; pixels are drawn two to a
cell with half blocks. The images and canvas options turn either
off, the csp option narrows what markup may load, as
new TermDOM({csp: "img-src https: data:"}), and a "fetch" listener
can answer each load, as a Service Worker does.TermDOM implements the browser's rendering pipeline against a grid of character
cells instead of pixels. The cell is the unit basis for CSS lengths: by
default 1px and 1ch both mean one cell. On each frame the engine recomputes style and layout for
whatever mutated, paints the result into a cell buffer, diffs it against the
previous frame, and writes the difference to stdout as ANSI escape sequences.
Escape sequences from stdin are decoded into keyboard, mouse, and paste events
and dispatched to DOM nodes.
markdown.ts — a Markdown viewer that pages
when the document is taller than the terminal.chat.ts — a streaming LLM chat client powered by
ch.at, with a transcript and composer.todomvc.ts — the official TodoMVC with its
component logic unmodified; only the stylesheet was swapped.fuzzy-finder.ts — a file picker that
prints the selection to stdout.weather.ts — an emoji forecast from
Open-Meteo, with a city search and flexbox day cards.popover.ts — a menu bar where every menu is
a declarative popover; open, dismiss and stacking are the platform's.codemirror.ts — CodeMirror editing a file on a
contenteditable host, with line numbers and syntax colors.solitaire.ts — the Klondike solitaire above,
with seeded deals playable by keyboard or mouse.hello-{react,vue,svelte,crank}.ts — one greeting and
keypress counter apiece, each driving that framework's stock renderer.ssh.ts — an SSH server where every session gets a
document of its own, rendered over the session's channel.shell.ts — ls, cat, less, cd and pwd
over the filesystem, which on termdom.org is this repository.images.ts — an <img> at its natural size and
under each object-fit; pass a path or URL to show your own image.canvas.ts — an animated 2D canvas beside
Conway's Life on a "termdom-cellgrid" canvas.More runnable examples can be found in examples/. Most of
them also run in the browser at
termdom.org/examples, from the same
files.
TermDOM runs on Node, Bun and Deno. The library has no native components and
can be used to create binaries with tools like bun build --compile.
COMPATIBILITY.md is generated by probing each feature against the engine.
Not to be confused with DomTerm by Per Bothner, a terminal emulator built out of DOM elements. The two projects are each other's inverse: DomTerm puts a terminal in the DOM; TermDOM puts the DOM in a terminal.
MIT
1,584 followers · starred Aug 2026
326 followers · starred Aug 2026
629 followers · starred Aug 2026
42 followers · starred Aug 2026
Build terminal apps with HTML, CSS and DOM.
See the codeBuild terminal apps with HTML, CSS, and DOM.

TermDOM is a JavaScript/TypeScript library that renders HTML and CSS to the terminal. It draws actual DOM nodes to terminal output and redraws the screen when they are mutated, so TUIs and interactive CLIs can be written with vanilla JavaScript, or any frontend web framework or library.
npm install @b9g/termdom
import {TermDOM} from "@b9g/termdom";
const term = new TermDOM();
term.attach();
// The document is a real DOM document.
const {document} = term;
document.body.innerHTML = `
<style>
.card { border: 1px solid #5fafff; padding: 0 1ch; width: 36ch; }
.title { color: #5fafff; font-weight: bold; }
progress { width: 25ch; }
progress::slider-fill { background-color: green; }
.pct { color: #888; }
</style>
<div class="card">
<div class="title">Installing</div>
<div>
<progress id="bar" max="100" value="0"></progress>
<span class="pct" id="pct"></span>
</div>
</div>
`;
// TermDOM observes mutations and re-renders automatically.
let n = 0;
setInterval(() => {
n = (n + 1) % 101;
document.querySelector("progress")!.value = n;
document.getElementById("pct")!.textContent = String(n).padStart(3) + "%";
}, 50);

<style> elements and style attributes cascades
and inherits as in the browser, translated to ANSI color and decoration.document.querySelector(), MutationObserver,
ResizeObserver, and getBoundingClientRect() work and report
cell-based layout.<input>, <textarea>, <select>, checkboxes, radios, and
sliders have terminal-native looks, restylable with CSS; Tab and
:focus work.overflow boxes taller than the terminal
scroll with the wheel, the arrow and page keys, window.scrollTo() and
element.scrollIntoView().customElements.define(), attachShadow(), <slot>,
:host, and scoped styles; the built-in controls are shadow trees.::selection; the caret moves
by grapheme, and text fields bind the readline chords (Ctrl+A/E/K/U/W).Element.requestFullscreen() renders to the alternate
screen; exiting restores the shell and its scrollback.<img> decodes PNG, JPEG, GIF and BMP on the
runtime's web Worker where there is one, and <canvas> has a 2D
context and a "termdom-cellgrid" context; pixels are drawn two to a
cell with half blocks. The images and canvas options turn either
off, the csp option narrows what markup may load, as
new TermDOM({csp: "img-src https: data:"}), and a "fetch" listener
can answer each load, as a Service Worker does.TermDOM implements the browser's rendering pipeline against a grid of character
cells instead of pixels. The cell is the unit basis for CSS lengths: by
default 1px and 1ch both mean one cell. On each frame the engine recomputes style and layout for
whatever mutated, paints the result into a cell buffer, diffs it against the
previous frame, and writes the difference to stdout as ANSI escape sequences.
Escape sequences from stdin are decoded into keyboard, mouse, and paste events
and dispatched to DOM nodes.
markdown.ts — a Markdown viewer that pages
when the document is taller than the terminal.chat.ts — a streaming LLM chat client powered by
ch.at, with a transcript and composer.todomvc.ts — the official TodoMVC with its
component logic unmodified; only the stylesheet was swapped.fuzzy-finder.ts — a file picker that
prints the selection to stdout.weather.ts — an emoji forecast from
Open-Meteo, with a city search and flexbox day cards.popover.ts — a menu bar where every menu is
a declarative popover; open, dismiss and stacking are the platform's.codemirror.ts — CodeMirror editing a file on a
contenteditable host, with line numbers and syntax colors.solitaire.ts — the Klondike solitaire above,
with seeded deals playable by keyboard or mouse.hello-{react,vue,svelte,crank}.ts — one greeting and
keypress counter apiece, each driving that framework's stock renderer.ssh.ts — an SSH server where every session gets a
document of its own, rendered over the session's channel.shell.ts — ls, cat, less, cd and pwd
over the filesystem, which on termdom.org is this repository.images.ts — an <img> at its natural size and
under each object-fit; pass a path or URL to show your own image.canvas.ts — an animated 2D canvas beside
Conway's Life on a "termdom-cellgrid" canvas.More runnable examples can be found in examples/. Most of
them also run in the browser at
termdom.org/examples, from the same
files.
TermDOM runs on Node, Bun and Deno. The library has no native components and
can be used to create binaries with tools like bun build --compile.
COMPATIBILITY.md is generated by probing each feature against the engine.
Not to be confused with DomTerm by Per Bothner, a terminal emulator built out of DOM elements. The two projects are each other's inverse: DomTerm puts a terminal in the DOM; TermDOM puts the DOM in a terminal.
MIT
1,584 followers · starred Aug 2026
326 followers · starred Aug 2026
629 followers · starred Aug 2026
42 followers · starred Aug 2026