Quickstart Guide
View SourceLet's build a terminal UI app. By the end of this page you'll have a working counter in your terminal.
What you'll learn:
- The four callbacks every Raxol app implements
- How to handle keyboard input and button clicks
- How the View DSL builds layouts
Install
Generate a new project:
Generate a new project:
mix raxol.new my_app
cd my_app
mix deps.get
Or add to an existing project:
# mix.exs
def deps do
[{:raxol, "~> 2.3"}]
endYour First App
Every Raxol app follows The Elm Architecture (TEA) with four callbacks:
defmodule MyApp do
use Raxol.Core.Runtime.Application
# 1. Initialize state
@impl true
def init(_context) do
%{count: 0}
end
# 2. Handle messages
@impl true
def update(message, model) do
case message do
:increment ->
{%{model | count: model.count + 1}, []}
:decrement ->
{%{model | count: model.count - 1}, []}
# Keyboard events
%Raxol.Core.Events.Event{type: :key, data: %{key: :char, char: "+"}} ->
{%{model | count: model.count + 1}, []}
%Raxol.Core.Events.Event{type: :key, data: %{key: :char, char: "-"}} ->
{%{model | count: model.count - 1}, []}
%Raxol.Core.Events.Event{type: :key, data: %{key: :char, char: "q"}} ->
{model, [command(:quit)]}
_ ->
{model, []}
end
end
# 3. Render UI from state
@impl true
def view(model) do
column style: %{padding: 1, gap: 1, align_items: :center} do
[
text("My Counter", style: [:bold]),
box style: %{border: :single, padding: 1, width: 20, justify_content: :center} do
text("Count: #{model.count}", style: [:bold])
end,
row style: %{gap: 1} do
[
button("+", on_click: :increment),
button("-", on_click: :decrement)
]
end,
text("Press +/- or click buttons. q to quit.", style: [:dim])
]
end
end
# 4. Subscriptions (optional)
@impl true
def subscribe(_model), do: []
end
# Start the app
{:ok, pid} = Raxol.start_link(MyApp, [])
ref = Process.monitor(pid)
receive do
{:DOWN, ^ref, :process, ^pid, _reason} -> :ok
endWhat's happening here?
init/1returns a plain map -- that's your entire app stateupdate/2pattern-matches on messages and returns{new_state, commands}-- the empty list[]means "no side effects"view/1builds the UI from state using the View DSL macros (column,row,box)command(:quit)is a built-in command that tells the runtime to shut down
Save as lib/my_app.ex and run:
mix run lib/my_app.ex
How It Works
+---> view(model) ---> Terminal
|
init(context) --+--> model
|
+---> update(message, model) --+
^ |
| {new_model, cmds} |
+------------------------+init/1sets up your initial state (the "model")view/1renders the UI -- it's called after every state changeupdate/2handles messages (keyboard events, button clicks, timers)subscribe/1sets up recurring events (timers, external data)
State flows in one direction. Views are pure functions of state. Side effects go through commands.
View DSL
The View DSL provides macros for building layouts:
# Layout containers
column style: %{gap: 1} do ... end # Vertical stack
row style: %{gap: 2} do ... end # Horizontal stack
# Widgets
text("Hello", style: [:bold]) # Text with styling
button("Click", on_click: :msg) # Clickable button
text_input(value: v, placeholder: "") # Text input
progress(value: 65, max: 100) # Progress bar
# Containers
box style: %{border: :single, padding: 1} do ... end # Bordered box
# Utilities
divider() # Horizontal line
spacer() # Flexible spaceAdding Live Updates
Use subscribe/1 to get periodic messages:
@impl true
def subscribe(_model) do
[subscribe_interval(1000, :tick)] # Send :tick every second
end
@impl true
def update(:tick, model) do
{%{model | uptime: model.uptime + 1}, []}
endOTP Supervision
Use --sup when generating to get a proper OTP application:
mix raxol.new my_app --sup
This generates an Application module with a supervision tree. Run with:
mix run --no-halt
What You Just Built
That counter is a complete Raxol app -- init/update/view is the whole API. Everything else builds on this loop.
Next steps:
- Widget Gallery -- All widgets with examples
- Core Concepts -- Buffers, rendering pipeline, and how it all fits together
- Building Apps -- Patterns for real apps (state machines, scrollable lists, keyboard shortcuts)
Explore Further
These features set Raxol apart from other TUI frameworks:
SSH App Serving -- Serve your app over SSH. Each connection gets its own process:
mix run examples/ssh/ssh_counter.exs
# Then: ssh localhost -p 2222
Hot Code Reload -- Edit your view function while the app is running:
iex -S mix run examples/dev/hot_reload_demo.exs
# Edit the view/1 function and save -- UI updates automatically
Crash Isolation -- Components run in separate processes. One crash doesn't take down the app:
mix run examples/components/process_component_demo.exs
Working examples to study:
examples/getting_started/counter.exs-- the counter from this pageexamples/demo.exs-- flagship demo with dashboard, sparklines, live statsexamples/apps/todo_app.ex-- a complete todo list app