termdom
Build Terminal UIs with HTML, CSS and DOM.
npm install @b9g/termdom
a real DOM, a real cascade, a real layout engine โ rendered to your terminal
TermDOM is a JavaScript library that displays HTML and CSS in the terminal. It draws actual DOM nodes to terminal output and redraws the screen when they mutate, so TUIs and interactive CLIs can be written with vanilla JavaScript or any frontend web framework.
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; }
.done { color: green; }
.rest { color: #444; }
.pct { color: #888; }
</style>
<div class="card">
<div class="title">Installing</div>
<div>
<span class="done" id="done"></span><span class="rest" id="rest"></span>
<span class="pct" id="pct"></span>
</div>
</div>
`;
// TermDOM observes mutations and re-renders automatically.
let n = 0;
setInterval(() => {
n = (n + 1) % 101;
const cells = Math.round(n / 4);
document.getElementById("done").textContent = "โ".repeat(cells);
document.getElementById("rest").textContent = "โ".repeat(25 - cells);
document.getElementById("pct").textContent = String(n).padStart(3) + "%";
}, 50);One cell is 1ch wide and 1px tall. Every box lands on whole cells.
Write a web page. Get a TUI. #
Every glyph below is a DOM element. The spinner is a <span> whose textContent mutates; painting is automatic, like the browser.
const spinner = document.createElement("span");
spinner.className = "spin"; // .spin { color: green }
section.appendChild(spinner);
setInterval(() => {
spinner.textContent = frames[n++ % frames.length];
}, 30); // no render call -- mutations paintInteractivity is just DOM events. #
A NERDTree-style file browser in ~200 lines of vanilla DOM: querySelectorAll for the rows, classList for the selection, keydown for the keys, and scrollIntoView() to move the camera.
document.addEventListener("keydown", (ev) => {
if (ev.key === "j") select(selected + 1);
if (ev.key === "Enter") expand(rows()[selected]);
});
rows()[selected].scrollIntoView();Real text input. Real caret. Real IME. #
<input> elements with focus traversal and :focus styling โ and the caret is the real terminal cursor, so CJK input methods compose in the field, measured in cells.
<div class="field">
<div class="label">Name</div><input id="name">
</div>
field.addEventListener("input", updatePreview);Features #
- Stylesheets CSS from
<style>elements andstyleattributes cascades and inherits like it does in the browser, and is translated to ANSI escapes for color and text decoration. - Layout The CSS box model, flexbox, and table layout. Sizes resolve to whole cells.
- Text CJK, emoji, and combining characters take their correct widths. Hebrew and Arabic render in visual order with contextual shaping, and the caret moves by grapheme.
- Scrolling Documents taller than the terminal scroll with
window.scrollTo()andelement.scrollIntoView(). - Events Events for keys, mouse, focus, and paste fire on elements, the document, and the window, pulled from STDIN.
- DOM utilities
document.querySelector(),MutationObserver,ResizeObserver, andElement.getBoundingClientRect()are hooked up to the layout engine and viewport, following browser standards. - Forms
<input>,<textarea>,<select>, checkboxes, and radios come with default behavior and terminal-native looks, and can be restyled with ordinary CSS. Tab navigation and:focusstyles are supported. - Web Components
customElements.define(),attachShadow(),<slot>,:host, and scoped styles behave like the browser's. The built-in form controls are themselves shadow trees. - Selection Drag to select, styled with
::selection. - Fullscreen
Element.requestFullscreen()renders an element to the alternate screen. Exiting restores the shell and its scrollback.
Compatibility #
The compatibility matrix is generated by a probe suite: each DOM API, selector, and CSS property is applied to a real document and rendered, and the table records whether the output changed.
Get started #
npm install @b9g/termdomGuides โ ยท Examples on GitHub โ
Name #
Not to be confused with DomTerm, Per Bothner's 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.