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
drawFrame
for 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_PATH
to 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_ffi
libraries inprebuilt/<platform-arch>/
and prefer loading them at runtime. You can also point to a locally builtratatui_ffi
viaRATATUI_FFI_PATH
. - CI publish environment: Our GitHub Actions publish job currently uses Node 18 to avoid transient
ffi-napi
build 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-napi
rebuilds 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/RowsCellsLines
to batch inputs efficiently. - Layout:
layoutSplitEx2
andlayoutSplitPercentages
helpers. - 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
) andFrameBuilder
for 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-napi
installed 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.txt
to 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 +handle
property 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 unionEvent
for convenience - Mouse:
mouseKind.*
,mouseButton.*
- Widgets:
widgetKind.*
- Colors:
- Blessed/Ink: great for Node.js text UIs.
ratatui-ts
targets high‑performance, retained‑mode widgets with robust layout, composable frames, and deterministic headless testing. - ncurses wrappers: low‑level control;
ratatui-ts
provides 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_PATH
to 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 freeTerminal
in afinally
block 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.
-
postinstall
will 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
postinstall
script that downloads the right asset ifRATATUI_FFI_PATH
is 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-napi
package.
- 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.