ratatui-go
September 12, 2025 · View on GitHub
High‑performance Go bindings for the ratatui_ffi C ABI — the stable C layer over the Rust Ratatui engine. Build rich terminal apps with fast rendering, a robust widget set, predictable input, headless snapshots, and Go‑native ergonomics designed to avoid allocations in hot paths.
What you get
- Widgets and layout: Paragraph, List (+state), Table (+state), Tabs, Gauge, LineGauge, BarChart, Sparkline, Chart, Clear, RatatuiLogo, Canvas, optional Scrollbar
- Rendering: draw into rects or batch multiple widgets via
Terminal.DrawFrame - Input: keyboard, mouse, resize (+ event injectors for tests)
- Headless snapshots: frame text, extended styles, or cells for testing
- Go ergonomics: Arena (per‑frame scratchpad), FrameBuilder, SmallSpans, Session helpers, Loop (tick), Keymap, Layout DSL (Percent/Min/Ratio/EqualN)
Requirements
- Go 1.21+
- Built
ratatui_ffishared library available at runtime- Linux:
libratatui_ffi.so - macOS:
libratatui_ffi.dylib - Windows:
ratatui_ffi.dll
- Linux:
Install / Build
- Build the Rust FFI library:
cargo build --release -p ratatui_ffi
- Fetch the native library (recommended)
Use go:generate to fetch the correct libratatui_ffi from GitHub Releases into ./native/. Add this line once to your app and run go generate ./... before go build (in CI or locally):
//go:generate go run github.com/holo-q/ratatui-go/tools/fetch_native@latest --version v0.4.0
Thanks to rpath ($ORIGIN on Linux, @loader_path on macOS), placing the library next to your binary makes it load with no env vars.
Alternative (manual)
- Add the library directory to your platform’s loader path, or copy the library next to your binary.
- Build/test your Go project that imports
github.com/holo-q/ratatui-go.
Tip: set RATATUI_FFI_LIB=/absolute/path/to/libratatui_ffi.so when running the audit tool.
Bundling the native library (no env vars)
- Use the helper to copy/download the correct library into
./native/:
# Copy from a local build
go run ./tools/fetch_native --lib ../ratatui_ffi/target/release/$(
case "$(uname -s)" in Linux) echo libratatui_ffi.so ;; Darwin) echo libratatui_ffi.dylib ;; *) echo ratatui_ffi.dll ;; esac)
# Or download from a URL hosting your prebuilt (optionally verify checksums)
go run ./tools/fetch_native --url https://example.com/releases/libratatui_ffi-<platform>.so \
--checksums-url https://example.com/releases/checksums.txt
Thanks to rpath, placing the library next to your app binary makes it load without LD_LIBRARY_PATH/DYLD_LIBRARY_PATH setup.
Or fetch from GitHub Releases with sane defaults:
- Defaults:
--repo holo-q/ratatui-ffi,--pattern=plain(assets named exactlylibratatui_ffi.*) - Pass
--versionto choose the release tag to fetch
go run ./tools/fetch_native --version v0.4.0
If your assets are suffixed per platform (e.g., libratatui_ffi-linux-x64.so):
go run ./tools/fetch_native.go --version v0.4.0 --pattern suffixed
Spec (explicitness)
- Defaults aim to “just work”. Override when needed:
--pattern suffixedfor assets namedlibratatui_ffi-<os-arch>.*(linux-x64, darwin-arm64, windows-x64)--repo org/nameif you host release assets elsewhere--urlfor fully explicit downloads
Quickstart
package main
import (
"fmt"
rt "github.com/holo-q/ratatui-go"
)
func main() {
maj, min, pat := rt.Version()
fmt.Printf("FFI: %d.%d.%d\n", maj, min, pat)
p := rt.NewParagraph()
defer p.Free()
p.AppendSpan("Hello Ratatui from Go!", rt.Style{FG: rt.ColorRGB(0, 255, 180)})
out, err := rt.HeadlessRenderParagraph(30, 3, p)
if err != nil { panic(err) }
fmt.Println(out)
}
Notes
- Ownership: free returned strings via the FFI (
ratatui_string_free) is handled by the wrapper; free widget handles explicitly or rely on finalizers. - Strings: Go strings are passed as UTF‑8 C strings; pointers remain alive for the duration of the call.
- Loading: We link to the shared library via cgo and rely on the platform loader (see Build step 2).
Validation
- Audit tool (no ffi_introspect):
go run ./tools/ffi_audit.go --lib /path/to/libratatui_ffi.so— dlsym check for symbols declared inffi.go; also uses nm/readelf/objdump if available to compare exports
- Headless snapshot tests: text + styles_ex + cells
Examples
examples/headless: render a Paragraph headlesslyexamples/frame: init a Terminal and draw a Paragraph via batched frameexamples/composite: compose multiple widgets and render headless text/styles/cellsexamples/canvas_and_logo: draw a simple Canvas (rect + line) and the Ratatui logo
Run:
go run ./examples/headless
go run ./examples/frame
go run ./examples/composite
go run ./examples/interactive
go run ./examples/canvas_and_logo
Single entrypoint (one‑liner)
- If native lib isn’t set up, this prints friendly setup steps instead of a linker error:
go run github.com/holo-q/ratatui-go/cmd/demos@v0.1.2
- After setup, run the real demo with the ffi tag:
go run -tags ffi github.com/holo-q/ratatui-go/cmd/demos@v0.1.2
First run setup (once per machine)
go run github.com/holo-q/ratatui-go/tools/fetch_native@latest --lib /path/to/libratatui_ffi.* --out $HOME/.local/lib
# Linux: export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH" (and LIBRARY_PATH for link)
# macOS: export DYLD_LIBRARY_PATH="$HOME/.local/lib:$DYLD_LIBRARY_PATH" (and LIBRARY_PATH for link)
# Windows: add %USERPROFILE%\.local\lib to PATH
Tests (tagged)
Headless snapshot test is behind a build tag to avoid cgo link errors when the native lib is absent.
Run with the ffi tag and ensure the library is discoverable via your platform loader paths:
go test -tags ffi ./...
go:generate (recommended, minimal)
Add one line to your app. Then run go generate ./... before go build (in CI or locally). Replace <module-path> and version with your target:
//go:generate go run github.com/holo-q/ratatui-go/tools/fetch_native@latest --version v0.4.0
That fetches the correct libratatui_ffi for your platform from GitHub Releases into ./native/.
Thanks to rpath, your built app runs with no env vars.
Notes
- Plain asset names (default):
libratatui_ffi.so/.dylib/ratatui_ffi.dll→ just pass--version. - Suffixed names (e.g.,
libratatui_ffi-linux-x64.so): add--pattern suffixed.
Feature bits and optional APIs
Use FeatureBits() to gate optional functionality at runtime:
- STYLE_DUMP_EX: extended styles dump via
HeadlessRenderFrameStylesEx - CANVAS: canvas APIs (
Canvaswidget) - SCROLLBAR:
Scrollbarwidget — ratatui-go uses runtime symbol lookup; constructors return errors if unavailable - AXIS_LABELS / batching flags: chart axis labels or batched table/list helpers (no‑op safe)
Loading the native library
The cgo bindings link against -lratatui_ffi and rely on your platform’s loader to resolve it. Common setups:
- Add the build directory to loader paths (
LD_LIBRARY_PATH/DYLD_LIBRARY_PATH/PATH) - Copy the library next to your binary for deployment
- For auditing only, pass
--libto the audit tool (orRATATUI_FFI_LIB)
API overview
Terminal
- Lifecycle:
NewTerminal(),(*Terminal).Free() - Draw:
DrawParagraphIn,DrawFrame([]DrawCmd) - Modes:
EnableRaw/DisableRaw,EnterAlt/LeaveAlt,ShowCursor - Size/Viewport:
Size,Set/GetViewportArea
Widgets
- Paragraph/List/Table (+state), Tabs, Gauge/LineGauge, BarChart/Sparkline, Chart, Clear, RatatuiLogo, Canvas, optional Scrollbar
- Spans‑based block titles available across widgets; Arena variants avoid per‑call allocations
Events
NextEvent(timeoutMs) (Event, ok)— kinds: Key/Resize/Mouse- Injectors for tests:
InjectKey/InjectResize/InjectMouse
Headless snapshots
- Frame text, extended styles, or structured cells
- Per‑widget headless helpers (e.g., RenderParagraph, RenderChart)
Layout DSL
Px, Percent, MinPx, RatioOf, EqualN, andSplitEx2/Percentages- Compose 1..N constraints for any split; combine rows/columns by nesting
Go ergonomics
- Arena (per‑frame scratchpad) to batch span allocations and free once per frame
- FrameBuilder to construct
[]DrawCmdover a preallocated buffer - SmallSpans (stack‑backed) for common small spans without heap
- Session helpers:
WithTerminal,WithSession(raw/alt/clear), scopedWithCursorHidden,WithViewport - Loop (tick‑based) and Keymap (static dispatch) for snappy loops
See also:
- docs/ergonomics.md — Arena, FrameBuilder, SmallSpans patterns
- docs/layout-dsl.md — composing complex splits with DSL
- docs/session-loop-keymap.md — sessions, loops, keymaps
- docs/feature-bits.md — optional features and gating
Enums (quick reference)
- Align: Left=0, Center=1, Right=2
- Direction (layout): Vertical=0, Horizontal=1
- Borders (bitmask): LEFT=1, RIGHT=2, TOP=4, BOTTOM=8 (combine with |)
- BorderType: Plain=0, Thick=1, Double=2
— The Ratatui Bindings Team
Contribute
We welcome sharp eyes and strong opinions. If something feels off, open an issue and tell us exactly why — then tell us how you’d improve it.
-
File issues with specifics:
- What’s not good yet and why (be concrete).
- Repro steps, expected vs. actual behavior, terminal output.
- Your improvement proposal: API shape, UX copy, code structure, or examples.
- Scope and priority if you can (quick win vs. deeper refactor).
-
Agents process issues. You, the human, are our master QA:
- Run the demos/examples (use the README instructions) and report results.
- Include OS, terminal, Go version, and how you set up the native library.
- Attach screenshots or short recordings when visuals matter.
-
Documentation and structure need a human touch:
- Propose clarity edits to README/docs, better example layout, or naming tweaks.
- PRs with concrete wording and structure improvements are highly valued.
-
PR expectations (keep it simple):
- Tight scope, consistent style, no drive‑by unrelated changes.
- If you run demos locally, remember the native lib and (optionally)
-tags ffi. - CI builds library packages by default; examples run in the ffi job.
We’d rather get high‑signal issues/PRs than perfection. Tell us what hurts and how you’d fix it — our agents will do the heavy lifting.
Phase 2: Go ergonomics roadmap (zero‑alloc friendly)
- FrameArena: per‑frame scratchpad that builds C spans once and frees at frame end.
- FrameBuilder: stack‑friendly draw command builder with preallocated buffer.
- SmallSpans: stack‑backed span builders (first N spans on stack, no heap).
- Session helpers:
WithTerminal(func(*Terminal){})for raw/alt/cursor scoping. - Loop helper: tiny driver with tick budget and cooperative yielding.
- Keymap: table‑driven handler dispatch without per‑loop allocations.
- Scoped cursor/viewport setters using defer to restore state.
These APIs are opt‑in and keep performance front‑and‑center: no hidden goroutines, no unnecessary allocations, and clear lifetimes. See examples/interactive and upcoming examples/ergonomics for patterns.