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)
- Go 1.21+
- Built
ratatui_ffi
shared library available at runtime- Linux:
libratatui_ffi.so
- macOS:
libratatui_ffi.dylib
- Windows:
ratatui_ffi.dll
- Linux:
- 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
--version
to 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 suffixed
for assets namedlibratatui_ffi-<os-arch>.*
(linux-x64, darwin-arm64, windows-x64)--repo org/name
if you host release assets elsewhere--url
for fully explicit downloads
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)
}
- 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).
- 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/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
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 ./...
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
.
Use FeatureBits()
to gate optional functionality at runtime:
- STYLE_DUMP_EX: extended styles dump via
HeadlessRenderFrameStylesEx
- CANVAS: canvas APIs (
Canvas
widget) - SCROLLBAR:
Scrollbar
widget — 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)
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
--lib
to the audit tool (orRATATUI_FFI_LIB
)
- Lifecycle:
NewTerminal()
,(*Terminal).Free()
- Draw:
DrawParagraphIn
,DrawFrame([]DrawCmd)
- Modes:
EnableRaw/DisableRaw
,EnterAlt/LeaveAlt
,ShowCursor
- Size/Viewport:
Size
,Set/GetViewportArea
- 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
NextEvent(timeoutMs) (Event, ok)
— kinds: Key/Resize/Mouse- Injectors for tests:
InjectKey/InjectResize/InjectMouse
- Frame text, extended styles, or structured cells
- Per‑widget headless helpers (e.g., RenderParagraph, RenderChart)
Px, Percent, MinPx, RatioOf, EqualN
, andSplitEx2/Percentages
- Compose 1..N constraints for any split; combine rows/columns by nesting
- Arena (per‑frame scratchpad) to batch span allocations and free once per frame
- FrameBuilder to construct
[]DrawCmd
over 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
- 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
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.
- 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.