Skip to content
cd ..

TUIs everywhere

Really. Everywhere.

elixir beam tuis

Back in February I introduced ex_ratatui. It literally was “a very first version”. Five widgets and the LiveView callbacks we know, but for the terminal.

That was 0.1. We’re on 0.10 now and the library grew up a lot. A couple dozen widgets, a second runtime, images, theming, telemetry… the usual story of a thing getting pushed in directions you didn’t plan for. Cool, but honestly that’s not the part I’m most excited about.

Because here’s the thing. Once your TUI is just an ExRatatui.App, then where it renders is a totally separate question from what it does. The app has no idea if it’s drawing to your terminal, to a browser tab, or to a screen the size of a postcard. And once you realise that, you stop asking “can I build a TUI” and start asking “ok, where in the BEAM world can I put one”. Turns out… pretty much everywhere.

BEAM superpowers

The rendering bit is the least interesting part of all this, because that problem is already solved. ratatui is doing the drawing under the hood, and it’s a gorgeous, mature, very actively developed library. People build genuinely wild stuff with it. I didn’t need to reinvent or think on any of that, just bridge a polished rendering engine for free.

And we get it through a NIF, which is a superpower on its own. Talking to other languages is something the BEAM is really good at, and a NIF is the tightest way to do it. Rendering a frame is short, CPU-bound work that returns right away, so it never blocks a scheduler. Elixir hands ratatui the widgets, ratatui hands back the cells, all in the same memory. No port, no shelling out, no copying data around. And since it’s precompiled (via rustler_precompiled), you don’t need Rust installed at all.

Now, think about it. Most TUIs are one process drawing to one terminal for one person, and when it dies, it’s gone. Here? It’s a supervised process, so it crashes and comes right back. It’s observable, so you can peek inside while it runs. You can attach to it remotely. You can run the exact same app for one person locally and for N people over SSH, isolated, without touching a line of code. You can share live state between a terminal and a browser over PubSub and barely think about it. And the list keeps going.

So a TUI on its own is a nice escape hatch. A TUI with supervision, observability, distribution and remote attach sitting underneath it is a long-lived, multi-user, multi-surface thing. Totally different beasts.

The corners

Same app module unchanged, running all over the place. You probably already live on some of these.

The terminal, obviously. The default, home base.

Over SSH. One option and your TUI becomes a remote surface, each client in its own isolated session. Perfect for an admin panel or a fleet dashboard you want to reach without standing up any web. Way more secure.

Over Erlang distribution. You attach to a TUI running on a remote node, and that node needs zero native code. It runs the callbacks, your machine does the rendering. For a headless or cross-arch box that’s just lovely.

On Nerves. Definitely a first-class must have from the beginning. Embedded is such a good home for TUIs. A little device with a screen and a dashboard on it reachable over IEx console, or as an SSH subsystem for nerves_ssh, or by attaching into the Erlang cluster.

In Livebook. kino_ex_ratatui drops an app right into a notebook cell. Same app, now clickable in Livebook. Amazing for just messing around without leaving the notebook.

Within Phoenix LiveView. phoenix_ex_ratatui renders the same module within LiveView. Full page or tucked in next to your normal views and HTML. No terminal emulator in the browser, it ships the rendered cells down the socket and a tiny hook paints them as HTML spans.

On an e-ink screen. I put together a PR that drives the display of Goatmire’s name_badge using ex_ratatui. No terminal anywhere. It takes the grid of rendered cells and rasterizes them onto the panel through a tiny bitmap font.

Those last two work thanks to a small thing called CellSession. Instead of spitting out terminal escape codes, it just hands you the grid of cells, snapshots and diffs. Anything that can paint characters into a grid can be a target. Browsers can. E-ink can. Probably stuff I don’t even know of.

Code BEAM Europe 2026

So, I’m going to be talking about all of this at Code BEAM Europe 2026 🎉. The talk’s called TUIs everywhere, and I’m genuinely thrilled it got in.

The idea is to walk through when a TUI is actually the right answer, what’s possible today, and why OTP makes the whole thing more fun than it tends to be elsewhere. Hopefully there’ll be a funny live demo. More on that closer to the date.

What’s next

More of the same, honestly. More places to render, more widgets, more polish, more rough edges sanded down.

And more TUIs of course! ash_tui, bb_tui and a bunch of ideas in the bag.

If you build something with it I’d love to hear about it! The forum thread has been a great spot for that. Issues and PRs as always more than welcome.

Happy TUI coding 🍻.