Skip to content
cd ..

Introducing ex_ratatui

Cook up delicious terminal user interfaces in Elixir.

elixir rust terminal

ex_ratatui lets you build rich terminal UIs in Elixir using a LiveView-inspired API, powered by Rust’s ratatui under the hood.

The story

So I’m a pro-terminal user. Maybe you already noticed it!?

And I love TUIs (terminal user interfaces) as they allow you to do your own stuff in your own way and not leave the terminal ever! That’s the whole idea, isn’t it? And a browser, ok, yes.

Well, I met ratatui a while ago when I was on my Rust-heavy times, and got impressed by how easy it was to build stuff. It just worked like a charm. I even built some nice TUI for the company I was working for to inspect PDF files and parse them into the app.

Then one day I was looking to build some slides on the terminal and started using ratatui again (for that I actually really recommend - presenterm). But then I thought… why not use Elixir to build the TUI with the LiveView API that we all know, and just leverage ratatui behind the scenes to do its thing?

So yeah. That’s how ex_ratatui was born.

How it works

mount, render, handle_event — the callbacks you already know from LiveView, but for the terminal. Add it to your supervision tree and you’re done.

defmodule Counter do
  use ExRatatui.App

  alias ExRatatui.Layout
  alias ExRatatui.Layout.Rect
  alias ExRatatui.Style
  alias ExRatatui.Widgets.{Block, Paragraph}

  @impl true
  def mount(_opts) do
    {:ok, %{count: 0}}
  end

  @impl true
  def render(%{count: count}, frame) do
    [header, body, footer] =
      Layout.split(
        %Rect{x: 0, y: 0, width: frame.width, height: frame.height},
        :vertical,
        [{:length, 3}, {:min, 0}, {:length, 1}]
      )

    title = %Paragraph{
      text: "ExRatatui Counter",
      style: %Style{fg: :cyan, modifiers: [:bold]},
      alignment: :center,
      block: %Block{borders: [:all], border_type: :rounded}
    }

    counter = %Paragraph{
      text: "Count: #{count}\n\n↑/↓ to change, q to quit",
      style: %Style{fg: :green},
      alignment: :center,
      block: %Block{
        title: " Try it! ",
        borders: [:all],
        border_style: %Style{fg: :yellow}
      }
    }

    hint = %Paragraph{
      text: "Built with ExRatatui 🍻",
      style: %Style{fg: :dark_gray},
      alignment: :center
    }

    [{title, header}, {counter, body}, {hint, footer}]
  end

  @impl true
  def handle_event(%ExRatatui.Event.Key{code: "q"}, state), do: {:stop, state}
  def handle_event(%ExRatatui.Event.Key{code: "up"}, state), do: {:noreply, %{state | count: state.count + 1}}
  def handle_event(%ExRatatui.Event.Key{code: "down"}, state), do: {:noreply, %{state | count: state.count - 1}}
  def handle_event(_event, state), do: {:noreply, state}
end

And here it is in action:

ExRatatui counter demo

What’s next

It’s a very first version. There are ways to improve this in all directions. There’s plenty to consider: more widgets, better event handling, better distribution, you name it.

What makes Elixir great is the community, so everything is extremely absolutely more than welcome! Open an issue, send a PR, or just try it out and let me know what you think.

Happy TUI coding!