High‑performance, cross‑platform Terminal UI (TUI) for TypeScript/Node.js.
ratatui-ts exposes the battle‑tested Rust Ratatui engine over a stable C ABI
with idiomatic TS helpers for widgets, layout, batched frames, and headless
snapshot rendering. Works on Linux, macOS, and Windows.
- Rich widgets: Paragraph, List (stateful), Table (stateful), Gauge, Tabs, BarChart, Sparkline, Chart, Scrollbar (feature‑gated), Canvas, LineGauge.
- Events: keyboard and mouse (down/up/drag/move/scroll + modifiers), resize.
- Rendering: draw into rects; batched
drawFramefor multiple widgets. - Testing: headless renderers for widgets and composite frames; styles_ex and structured cell dumps.
- Ergonomics: builders for spans/lines/rows, layout helpers, FrameBuilder, color helpers.
- Coverage: 100% FFI parity guard via introspector JSON.
- Performance first: rendering, layout, and input run in native Rust; the TS layer is thin and predictable.
- Production‑grade testing: headless text, style, and cell snapshots keep UIs stable across refactors.
- Developer‑friendly: typed enums, builder helpers, and batched frame APIs reduce ceremony and round‑trips.
- Stability: explicit, flat C ABI; a coverage checker guarantees evolution without drift.
- Build the Rust cdylib first:
cargo build --release -p ratatui_ffi
This produces a platform-specific dynamic library in ratatui-ffi/target/release/:
- Linux:
libratatui_ffi.so - macOS:
libratatui_ffi.dylib - Windows:
ratatui_ffi.dll
- Install from npm (bindings only):
npm install ratatui-ts
If your library is not in the default search location, set:
RATATUI_FFI_PATHto the absolute path of the compiled library file
- Native layer choice: These bindings use
ffi-napi/ref-napi. They are widely used but can be sensitive to Node.js header/ABI shifts and node-gyp toolchains in clean CI environments. - Prebuilts first: We ship prebuilt
ratatui_ffilibraries inprebuilt/<platform-arch>/and prefer loading them at runtime. You can also point to a locally builtratatui_ffiviaRATATUI_FFI_PATH. - CI publish environment: Our GitHub Actions publish job currently uses Node 18 to avoid transient
ffi-napibuild breakage on newer Node releases when npm attempts to build from source. - Local dev guidance: Node 18–20 are recommended. On bleeding‑edge Node (e.g., 24.x), if
ffi-napirebuilds and fails, use an LTS Node (18/20) vianvm, or skip rebuilding entirely by using the shipped prebuilts withRATATUI_FFI_PATH. - Why you might see node-gyp errors: Some ecosystems deps (e.g.,
get-uv-event-loop-napi-h,node-addon-api) periodically tighten types or change headers; this can surface as C++ signature/const‑qualification errors when rebuildingffi-napi. These do not affect the correctness of Ratatui itself; they’re build‑time friction when a rebuild is attempted.
- Full scene (layout + chart + canvas):
examples/full-scene.ts - Batch widgets (paragraph/list/tabs/table/chart/bar/spark):
examples/batch-widgets.ts - Minimal terminal loop:
examples/terminal-loop.ts
See more in docs/EXAMPLES.md.
You can also try the interactive demo directly once published:
npx -y ratatui-ts-demos
Note on Node versions: This demo uses a native addon layer (ffi-napi). On bleeding‑edge Node.js (e.g., 24.x), npx may fail if the addon hasn’t released a compatible build yet. That’s not your app — it’s the system Node vs. native addon mismatch. For a quick try, use an LTS Node (18/20) or run with a project‑local Node version. Prefer a different vibe? The Python and C# bindings offer equally polished demos and headless tests:
- Python: https://github.com/holo-q/ratatui-py
- .NET/C#: https://github.com/holo-q/Ratatui.cs
These are generated by CI from our headless renderers after a green build. They provide a quick visual seal that widgets render correctly end‑to‑end.
Paragraph
|
Table
|
Chart
|
Combined
|
import {
Terminal, Paragraph, List, Table, Gauge, Tabs, BarChart, Sparkline, Chart,
color, styleMods, key, eventKind, mouseKind, mouseButton, widgetKind, rect,
headlessRender, headlessRenderFrame,
} from 'ratatui-ts';
// Terminal lifecycle
const term = Terminal.init();
try {
// Draw a Paragraph full-screen
const p = Paragraph.fromText('Hello from ratatui!');
p.setBlockTitle('Demo', true);
term.drawParagraph(p);
// Poll events (500ms timeout)
const evt = Terminal.nextEvent(500);
if (evt && evt.kind === eventKind.Key) {
if (evt.key.code === key.Enter) {
console.log('Enter pressed');
}
}
} finally {
term.free(); // restore terminal state
}
// Headless rendering (for tests/CI)
const p2 = Paragraph.fromText('Boxed text');
// show borders and a title
p2.setBlockTitle('Box', true);
const out = headlessRender(20, 3, p2);
console.log(out);- By default the loader tries:
RATATUI_FFI_PATH(if set)../ratatui-ffi/target/release/<libname>relative to this package../../ratatui-ffi/target/release/<libname>as a fallback
You can also explicitly pass a path to loadLibrary(path).
- Full widget surface: Paragraph, List (stateful), Table (stateful), Gauge, Tabs, BarChart, Sparkline, Chart, Scrollbar (feature‑gated), Canvas, LineGauge.
- Batched frames:
drawFrame(term, cmds)andheadlessRenderFrame(w,h,cmds). - Builders:
buildSpans/LineSpans/CellsLines/RowsCellsLinesto batch inputs efficiently. - Layout:
layoutSplitEx2andlayoutSplitPercentageshelpers. - Headless: per‑widget helpers plus styles_ex and structured cells for precise snapshots.
- Version/feature:
getVersion()andgetFeatureBits(); color helpers:colorHelper.rgb/idx.
- Linux: x64 tested; aarch64 supported via native build or prebuilts.
- macOS: Apple Silicon and Intel via
dylib. - Windows:
ratatui_ffi.dll.
- Batch builders for spans/lines/rows to minimize FFI calls.
- Layout helpers (
layoutSplitEx2,layoutSplitPercentages) andFrameBuilderfor batched drawing. - Headless helpers for full frames: text, styles_ex, and structured cells.
- Rich widget APIs (blockAdv, title alignment, batch setters) while staying 1:1 with FFI.
- Version and feature bits (
getVersion(),getFeatureBits()), color helpers (colorHelper.rgb/idx).
See the full feature guide: docs/TS-FEATURES.md. For the broader quality roadmap: docs/QUALITY-ROADMAP.md.
- Generate introspection JSON from the Rust side:
cd ratatui-ffi && cargo run --quiet --bin ffi_introspect -- --json > /tmp/ffi.json
- Run the TS coverage checker (fails on missing bindings):
node scripts/check-introspection.js /tmp/ffi.json --features-map scripts/features-map.json
- Optional: gate by feature bits (requires
ffi-napiinstalled in this repo and a compiled library path):
node scripts/check-introspection.js /tmp/ffi.json --lib ./ratatui-ffi/target/release/libratatui_ffi.so --features-map scripts/features-map.json
- Use
--allow path/to/allow.txtto temporarily silence known gaps (one export name per line). The script exits non-zero on any missing or invalid declarations.
- Classes
Terminal:init(),clear(),size(), per-widgetdrawXxxIn(),free(), staticnextEvent(timeout)returns a typed union (Event).Paragraph:fromText(),setBlockTitle(),appendLine(),free().List,Table,Gauge,Tabs,BarChart,Sparkline,Chart,Scrollbar(optional): predictableset...andfree()methods +handleproperty for batching.- Batched:
makeDrawCmd(kind, handle, rect),drawFrame(term, cmds).
- Headless helpers
headlessRender(width, height, paragraph)headlessRenderFrame(width, height, cmds)headlessRenderXxx(...)for most widgets
- Enums
- Colors:
color.* - Styles:
styleMods.* - Keys:
key.*(includes F1..F12 via numeric codes) - Events:
eventKind.*; typed unionEventfor convenience - Mouse:
mouseKind.*,mouseButton.* - Widgets:
widgetKind.*
- Colors:
- Blessed/Ink: great for Node.js text UIs.
ratatui-tstargets high‑performance, retained‑mode widgets with robust layout, composable frames, and deterministic headless testing. - ncurses wrappers: low‑level control;
ratatui-tsprovides modern widgets, layout primitives, and snapshot‑friendly rendering.
- Dual build is provided. Use either:
- ESM:
import { Terminal } from 'ratatui-ts'(resolves todist/esm/index.js) - CJS:
const { Terminal } = require('ratatui-ts')(resolves todist/cjs/index.js)
- ESM:
- For
BarChart.setValues()andSparkline.setValues(), you may passbigint[]ornumber[]. - Values are marshalled as true unsigned 64-bit (little-endian) to avoid precision loss.
- Snapshot‑style tests can use headless helpers to render into strings without a TTY.
- Example (Vitest): see
test/paragraph.spec.ts. - Tests auto‑skip if the native library is not found. Set
RATATUI_FFI_PATHto enable execution on CI.
- Short‑lived scripts: GC finalizers free native objects automatically.
- Long‑running apps and hot loops: call
.free()when done with widgets; always freeTerminalin afinallyblock to restore raw/alt/cursor. - Builders (
buildSpans/LineSpans/...) do not require manual free; they keep inputs alive only for the duration of the call.
See docs/RESOURCE-MANAGEMENT.md for guidance and examples.
postinstallwill check for the native library in common locations and warn if not found. It does not build the Rust code for you.- Build the Rust side separately, or ship prebuilt binaries for your target platforms.
- If you plan to publish to npm with prebuilt native libraries, consider:
- Adding release assets from your CI (e.g., GitHub Actions) covering Linux/macOS/Windows and architectures.
- A
postinstallscript that downloads the right asset ifRATATUI_FFI_PATHis not set, with checksum verification. - Keep the dynamic library outside your JS bundle; point the loader to it via
RATATUI_FFI_PATH.
- For maximum performance and fewer allocations, a Node-API (napi-rs) addon can wrap the same Rust logic.
- This repo focuses on C ABI + ffi-napi for portability and simplicity. If you want the addon path, we can scaffold a separate crate and
@ratatui/ts-napipackage.
- Python: https://github.com/holo-q/ratatui-py — typed Python bindings with docs site, demos, and utilities for responsive apps.
- .NET/C#: https://github.com/holo-q/Ratatui.cs — high‑performance C# API with headless snapshots and per‑RID prebuilts.



