mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applications that run under OTP supervision — without blocking the BEAM.
Why?
I wanted to build terminal UIs in Elixir with the same ergonomics we’re used to from LiveView. The existing options in the ecosystem are either stale (ratatouille hasn’t been updated in years) or focused on CLI output rather than full-screen interactive apps. Meanwhile, ratatui is one of the most actively maintained TUI libraries in any language — so bridging it to Elixir felt like the right approach.
What it looks like
The ExRatatui.App behaviour uses LiveView-inspired callbacks — mount/1, render/2, handle_event/2, and handle_info/2:
defmodule MyCounter do
use ExRatatui.App
@impl true
def mount(_opts), do: {:ok, %{count: 0}}
@impl true
def render(state, frame) do
alias ExRatatui.Widgets.Paragraph
alias ExRatatui.Layout.Rect
widget = %Paragraph{text: "Count: #{state.count}"}
[{widget, %Rect{x: 0, y: 0, width: frame.width, height: frame.height}}]
end
@impl true
def handle_event(%ExRatatui.Event.Key{code: "up"}, state),
do: {:noreply, %{state | count: state.count + 1}}
def handle_event(%ExRatatui.Event.Key{code: "q"}, state),
do: {:stop, state}
def handle_event(_event, state),
do: {:noreply, state}
end
# Add to your supervision tree
children = [{MyCounter, []}]
Supervisor.start_link(children, strategy: :one_for_one)
Features
- 5 widgets (so far): Paragraph, Block, List, Table, Gauge — with Block composition on all of them
- Constraint-based layout engine — split areas by percentage, length, min, max, or ratio
- Non-blocking event polling — keyboard, mouse, and resize events on BEAM’s DirtyIo scheduler
- OTP-supervised apps via
ExRatatui.Appbehaviour - Full color support — 17 named colors, RGB, and 256-color indexed
- Headless test backend — render to an in-memory buffer for CI-friendly testing
- Precompiled NIF binaries for Linux, macOS, and Windows — no Rust toolchain needed
Installation
def deps do
[{:ex_ratatui, "~> 0.4"}]
end
Precompiled binaries are downloaded automatically. No Rust toolchain required.
Examples
The repo includes several examples you can run directly:
mix run examples/hello_world.exs— minimal paragraph displaymix run examples/counter.exs— interactive counter with key eventsmix run examples/counter_app.exs— counter using the App behaviourmix run examples/system_monitor.exs— system dashboard (CPU, memory, disk, network, BEAM stats)mix run examples/task_manager.exs— full task manager using all widgetsexamples/task_manager/— a complete supervised Ecto + SQLite CRUD app with a TUI interface
What’s next
More widgets (Tabs, Sparkline, BarChart, Scrollbar), rich text primitives (mixed-style spans within a single widget), custom widgets. Beyond that — a theming system, periodic handle_tick callbacks, viewport modes for inline rendering, single-binary distribution via Burrito, etc etc.
The precompiled NIFs already target ARM and RISC-V, so running TUI apps on Nerves devices over SSH should be a natural fit? I haven’t played with it yet!
The issues list is the place to go — ratatui has a huge surface area, so there’s a lot of room to grow.
Contributions are very welcome!!!
Links
I’d love to hear what people think and what you’d want to build with it.
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
dbaer
Love this! After using IBM AS400 at work I had the thought of a TUI over SSH and this library looks like it fits the bill (DynamicSupervisor with Erlang’s built in ssh server?)
Ran into a minor crash with the
task_manager.exsexample, but it was an easy fix and I’ve submitted a PRmcass19
Quick update! ExRatatui is now at v0.5 with more widgets. And more in the makings…
Some projects built with it are starting to appear:
mix ash.tuiand you’re in.Feedback and contributions very welcome!
vegabook
Looks fantastic. Looking forward to the charting aspects being added especially the line charts.
mcass19
0.6 is out! Headline is a built-in SSH transport that lets you serve any
ExRatatui.Appmodule as a remote TUI over OTP:ssh. A single daemon hands each connected client its own isolated session; multiple clients can attach to the same app at the same time without stepping on each other.What it looks like
The simplest shape. Drop the daemon straight into a supervision tree:
Then from any other machine:
That’s it.
transport: :sshonExRatatui.Approutesstart_link/1through a newExRatatui.SSH.Daemoninstead of the local terminal path. The app module itself is unchange. It doesn’t know it’s being served over SSH.auto_host_key: trueis the other nice bit. More on that on the docs.Example: phoenix_ex_ratatui_example.
Integrating with
nerves_sshIf you’re already running
nerves_sshon a Nerves device you don’t need a second daemon.ExRatatui.SSHis an:ssh_server_channel, andnerves_sshtakes one through itssubsystems:list. There’s a helper for the tuple shape:Connect with:
The
-tis required — OpenSSH doesn’t allocate a PTY by default for subsystem invocations (sftpand similar binary protocols don’t need one), so without it your local terminal stays in cooked mode and keystrokes get line-buffered and locally echoed over the TUI. The subsystem name is the full Elixir module name as a charlist, so two different app modules configured into the same daemon get distinct names and don’t collide.Example: nerves_ex_ratatui_example.
What’s next
Reducer runtime for non-trivial apps. More distribution capabilites. More widgets. Contributions and TUIs out there are starting to appear :)!
ademenev
Maybe this is a shortcoming of the rust library, but support for double-width characters widely used in CJK languages is not complete. For example, if in text input there are some double-width characters then when pressing right arrow when the cursor is at the end of input makes the cursor not visible
mcass19
Hi @ademenev , thanks for the report! You’re right, this is a real gap on our side, not a ratatui shortcoming.
Ratatui handles wide characters correctly; our TextInput widget tracks the cursor and viewport in character counts while the widget width is in terminal cells, so the two drift apart as soon as any CJK character shows up.
I’ve opened an issue to track it here: Text input: cursor becomes invisible at end when line contains double-width (CJK) characters · Issue #45 · mcass19/ex_ratatui · GitHub
I’d be more than happy if you want to take a look and contribute to it. Otherwise I’ll pick it up soon. Either way, really appreciate your comment :).
mcass19
Hi all! A bunch of stuff shipped since the last update.
Highlights:
update/2+ commands + subscriptions, alongside the original callback runtime. Pick whichever fits your app.Span/Line) across every text-bearing widget.nerves_sshintegration, auto host-key bootstrap. Drop the daemon into a supervision tree and you’re in.ExRatatui.Appto remote BEAM nodes over Erlang distribution.Transportbehaviour for plugging in custom byte-stream carriers.kino_ex_ratatuiruns TUIs straight from a notebook. Honestly more fun than I expected :D!Readme, changelog, and examples have everything else: GitHub - mcass19/ex_ratatui: Elixir bindings for the Rust ratatui terminal UI library · GitHub .
Would love to hear your feedback and what you end up building with it.
johns10davenport
I’ll reach for this next time I need tui. I did a bit of ratatouille previously and it was not bad, but definitely could feel the staleness.
mcass19
Hi everyone! v0.10 is out.
Highlights:
ExRatatui.Widgets.Image, protocol-aware across every transport: pixel-perfect Kitty / Sixel / iTerm2 graphics locally, halfblocks fallback over SSH or in Livebook, and it even renders over Erlang distribution.ExRatatui.Theme, a pure-data palette struct with semantic slots,default/0/light/0constructors, and style helpers. No globals; apps thread it through render code.%Event.Paste{}instead of being shredded across keystrokes).ExRatatui.Focusgained mouse routing, so a click focuses the panel under the cursor.{:fill, weight}+ segment spacing in layouts, Table footers and column/cell highlighting, List direction / scroll padding, Style:underline_color,set_terminal_title/1, and more.Ecosystem:
CellSessionrenderer. Quite crazyCellSession, a WIP PR that puts ex_ratatui TUIs on a Nerves e-ink badge, rendering the cell grid straight to the display.README, changelog, and examples have everything else: GitHub - mcass19/ex_ratatui: Elixir bindings for the Rust ratatui terminal UI library · GitHub
As always, would love to hear feedback and what you build with it. Contributions extremely welcome everywhere!
jimsynz
I cannot overstate how amazing `bb_tui` is. I am super grateful @mcass19.