Elixir bindings for the Rust ratatui terminal UI library.
This module provides the core API for building terminal UIs: initializing the terminal, drawing widgets, and polling for events — all via Rust NIFs that run on the BEAM's DirtyIo scheduler.
Quick start
ExRatatui.run(fn terminal ->
{w, h} = ExRatatui.terminal_size()
paragraph = %ExRatatui.Widgets.Paragraph{text: "Hello!"}
rect = %ExRatatui.Layout.Rect{x: 0, y: 0, width: w, height: h}
ExRatatui.draw(terminal, [{paragraph, rect}])
ExRatatui.poll_event(60_000)
end)Core functions
run/1— initialize the terminal, run a function, restore on exitdraw/2— render a list of{widget, rect}tuples in a single framepoll_event/1— non-blocking event polling (keyboard, mouse, resize)terminal_size/0— current terminal dimensions
OTP apps
For supervised TUI applications, see ExRatatui.App — a behaviour with
LiveView-inspired callbacks (mount/1, render/2, handle_event/2).
Widgets
See ExRatatui.Widgets.Paragraph, ExRatatui.Widgets.Block,
ExRatatui.Widgets.List, ExRatatui.Widgets.Table,
ExRatatui.Widgets.Gauge, ExRatatui.Widgets.LineGauge,
ExRatatui.Widgets.Tabs, ExRatatui.Widgets.Scrollbar,
ExRatatui.Widgets.Checkbox, ExRatatui.Widgets.TextInput,
ExRatatui.Widgets.Clear, ExRatatui.Widgets.Markdown,
ExRatatui.Widgets.Textarea, ExRatatui.Widgets.Throbber,
ExRatatui.Widgets.Popup, and ExRatatui.Widgets.WidgetList.
Testing
Use init_test_terminal/2 and get_buffer_content/1 for headless
rendering verification in CI — no TTY required.
Summary
Functions
Draws a list of {widget, rect} tuples to the terminal in a single frame.
Returns the test terminal's buffer contents as a string.
Initializes a headless test terminal with the given dimensions.
Polls for terminal events with a timeout (default 250ms).
Runs a TUI application.
Returns the current terminal size as {width, height}.
Returns the current cursor position from the TextInput state.
Returns the current text value from the TextInput state.
Forwards a key event to the TextInput state.
Creates a new TextInput state.
Sets the text value on the TextInput state.
Returns the cursor position as {row, col} from the Textarea state.
Returns the current text from the Textarea as a string (lines joined with \n).
Forwards a key event with modifiers to the Textarea state.
Returns the number of lines in the Textarea state.
Creates a new Textarea state.
Sets the text value on the Textarea state.
Types
@type terminal_ref() :: reference()
@type widget() :: ExRatatui.Widgets.Paragraph.t() | ExRatatui.Widgets.Block.t() | ExRatatui.Widgets.Checkbox.t() | ExRatatui.Widgets.Clear.t() | ExRatatui.Widgets.List.t() | ExRatatui.Widgets.Table.t() | ExRatatui.Widgets.Gauge.t() | ExRatatui.Widgets.LineGauge.t() | ExRatatui.Widgets.Tabs.t() | ExRatatui.Widgets.Scrollbar.t() | ExRatatui.Widgets.Markdown.t() | ExRatatui.Widgets.Popup.t() | ExRatatui.Widgets.Textarea.t() | ExRatatui.Widgets.TextInput.t() | ExRatatui.Widgets.Throbber.t() | ExRatatui.Widgets.WidgetList.t()
Functions
@spec draw(terminal_ref(), [{widget(), ExRatatui.Layout.Rect.t()}]) :: :ok | {:error, term()}
Draws a list of {widget, rect} tuples to the terminal in a single frame.
Returns :ok on success or {:error, reason} on failure.
ExRatatui.draw(terminal, [
{%ExRatatui.Widgets.Paragraph{text: "Hello!"}, rect}
])
@spec get_buffer_content(terminal_ref()) :: String.t() | {:error, term()}
Returns the test terminal's buffer contents as a string.
Each line is trimmed of trailing whitespace and joined with newlines.
Only works with a test terminal reference from init_test_terminal/2.
@spec init_test_terminal(non_neg_integer(), non_neg_integer()) :: terminal_ref() | {:error, term()}
Initializes a headless test terminal with the given dimensions.
Uses ratatui's TestBackend — no real terminal needed. Useful for testing rendering output without a TTY. Returns a terminal reference.
Examples
iex> terminal = ExRatatui.init_test_terminal(40, 10)
iex> is_reference(terminal)
true
iex> terminal = ExRatatui.init_test_terminal(40, 10)
iex> alias ExRatatui.Widgets.Paragraph
iex> alias ExRatatui.Layout.Rect
iex> :ok = ExRatatui.draw(terminal, [{%Paragraph{text: "Hello!"}, %Rect{x: 0, y: 0, width: 40, height: 10}}])
iex> ExRatatui.get_buffer_content(terminal) =~ "Hello!"
true
@spec poll_event(non_neg_integer()) :: ExRatatui.Event.t() | nil | {:error, term()}
Polls for terminal events with a timeout (default 250ms).
Returns an Event.Key, Event.Mouse, Event.Resize struct, nil
if no event within the timeout, or {:error, reason} on failure.
@spec run((terminal_ref() -> term())) :: term() | {:error, term()}
Runs a TUI application.
Initializes the terminal, calls fun with the terminal reference,
and ensures terminal cleanup on exit.
ExRatatui.run(fn terminal ->
# your TUI loop here
end)
@spec terminal_size() :: {non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the current terminal size as {width, height}.
Returns {:error, reason} if the terminal size cannot be determined.
@spec text_input_cursor(reference()) :: non_neg_integer()
Returns the current cursor position from the TextInput state.
Examples
iex> state = ExRatatui.text_input_new()
iex> ExRatatui.text_input_cursor(state)
0
iex> ExRatatui.text_input_handle_key(state, "a")
:ok
iex> ExRatatui.text_input_cursor(state)
1
Returns the current text value from the TextInput state.
Examples
iex> state = ExRatatui.text_input_new()
iex> ExRatatui.text_input_get_value(state)
""
Forwards a key event to the TextInput state.
Pass the code field from an ExRatatui.Event.Key struct. Supports
printable characters, "backspace", "delete", "left", "right",
"home", and "end".
Examples
iex> state = ExRatatui.text_input_new()
iex> ExRatatui.text_input_handle_key(state, "h")
:ok
iex> ExRatatui.text_input_handle_key(state, "i")
:ok
iex> ExRatatui.text_input_get_value(state)
"hi"
@spec text_input_new() :: reference()
Creates a new TextInput state.
Returns a reference to the Rust-side state (ResourceArc). Pass this
reference as the :state field of %ExRatatui.Widgets.TextInput{}.
Examples
iex> state = ExRatatui.text_input_new()
iex> is_reference(state)
true
Sets the text value on the TextInput state.
The cursor is moved to the end of the new value.
Examples
iex> state = ExRatatui.text_input_new()
iex> ExRatatui.text_input_set_value(state, "hello")
:ok
iex> ExRatatui.text_input_get_value(state)
"hello"
@spec textarea_cursor(reference()) :: {non_neg_integer(), non_neg_integer()}
Returns the cursor position as {row, col} from the Textarea state.
Examples
iex> state = ExRatatui.textarea_new()
iex> ExRatatui.textarea_cursor(state)
{0, 0}
Returns the current text from the Textarea as a string (lines joined with \n).
Examples
iex> state = ExRatatui.textarea_new()
iex> ExRatatui.textarea_get_value(state)
""
Forwards a key event with modifiers to the Textarea state.
The textarea supports Emacs-style shortcuts (Ctrl+Z undo, Ctrl+Y redo, etc.).
Examples
iex> state = ExRatatui.textarea_new()
iex> ExRatatui.textarea_handle_key(state, "h", [])
:ok
iex> ExRatatui.textarea_handle_key(state, "i", [])
:ok
iex> ExRatatui.textarea_get_value(state)
"hi"
@spec textarea_line_count(reference()) :: non_neg_integer()
Returns the number of lines in the Textarea state.
Examples
iex> state = ExRatatui.textarea_new()
iex> ExRatatui.textarea_line_count(state)
1
@spec textarea_new() :: reference()
Creates a new Textarea state.
Returns a reference to the Rust-side state (ResourceArc). Pass this
reference as the :state field of %ExRatatui.Widgets.Textarea{}.
Examples
iex> state = ExRatatui.textarea_new()
iex> is_reference(state)
true
Sets the text value on the Textarea state.
Examples
iex> state = ExRatatui.textarea_new()
iex> ExRatatui.textarea_set_value(state, "hello\nworld")
:ok
iex> ExRatatui.textarea_get_value(state)
"hello\nworld"