Raxol Quickstart
View SourceGet started with Raxol in 5, 10, or 15 minutes. Choose your path based on what you need.
What is Raxol?
Raxol is a terminal UI framework for Elixir that scales from simple buffers to full applications:
- Raxol.Core - Lightweight buffer primitives (< 100KB, zero deps)
- Raxol.LiveView - Render terminals in Phoenix LiveView
- Raxol (full) - Complete framework with plugins and enterprise features
Start small, add features as needed.
5-Minute Tutorial: Your First Buffer
Just want to draw boxes and text? Use Raxol.Core.
Installation
# mix.exs
def deps do
[
{:raxol, "~> 2.0"} # Or {:raxol_core, "~> 2.0"} for minimal install
]
endmix deps.get
Hello Buffer
Create a file hello.exs:
alias Raxol.Core.{Buffer, Box}
# Create a 40x10 buffer
buffer = Buffer.create_blank_buffer(40, 10)
# Draw a double-line box
buffer = Box.draw_box(buffer, 0, 0, 40, 10, :double)
# Write some text
buffer = Buffer.write_at(buffer, 5, 4, "Hello, Raxol!")
# Render it
IO.puts(Buffer.to_string(buffer))Run it:
elixir hello.exs
Output:
╔══════════════════════════════════════╗
║ ║
║ ║
║ ║
║ Hello, Raxol! ║
║ ║
║ ║
║ ║
║ ║
╚══════════════════════════════════════╝That's it! Pure functional, no servers, no complexity.
Key Concepts (5 min version)
- Create -
Buffer.create_blank_buffer(width, height) - Draw -
Box.draw_box(),Box.fill_area(), etc. - Write -
Buffer.write_at(buffer, x, y, text) - Render -
Buffer.to_string(buffer)for output
Buffers are just data structures. No magic.
10-Minute Tutorial: LiveView Integration
Want to show a terminal in your Phoenix app? Add LiveView integration.
Add Dependency
# mix.exs
def deps do
[
{:raxol_core, "~> 2.0"},
{:raxol_liveview, "~> 2.0"},
{:phoenix_live_view, "~> 0.20 or ~> 1.0"}
]
endCreate a LiveView
# lib/my_app_web/live/terminal_live.ex
defmodule MyAppWeb.TerminalLive do
use MyAppWeb, :live_view
alias Raxol.Core.{Buffer, Box}
def mount(_params, _session, socket) do
# Create initial buffer
buffer = Buffer.create_blank_buffer(80, 24)
buffer = Box.draw_box(buffer, 0, 0, 80, 24, :rounded)
buffer = Buffer.write_at(buffer, 10, 10, "Hello from LiveView!", %{})
# Schedule periodic updates (optional)
if connected?(socket), do: Process.send_after(self(), :tick, 1000)
{:ok, assign(socket, buffer: buffer, count: 0)}
end
def render(assigns) do
~H"""
<div>
<h1>Live Terminal</h1>
<.live_component
module={Raxol.LiveView.TerminalComponent}
id="terminal"
buffer={@buffer}
theme={:nord}
on_keypress={&handle_keypress/1}
on_click={&handle_click/1}
/>
</div>
"""
end
def handle_info(:tick, socket) do
# Update buffer every second
count = socket.assigns.count + 1
buffer = Buffer.write_at(
socket.assigns.buffer,
10, 12,
"Ticks: #{count}",
%{fg_color: :cyan}
)
Process.send_after(self(), :tick, 1000)
{:noreply, assign(socket, buffer: buffer, count: count)}
end
def handle_keypress(key) do
IO.puts("Key pressed: #{key}")
end
def handle_click({x, y}) do
IO.puts("Clicked at: #{x}, #{y}")
end
endAdd Route
# lib/my_app_web/router.ex
scope "/", MyAppWeb do
pipe_through :browser
live "/terminal", TerminalLive
endInclude CSS
# lib/my_app_web/components/layouts/root.html.heex
<link rel="stylesheet" href={~p"/assets/raxol_terminal.css"} />Start Server
mix phx.server
# Visit http://localhost:4000/terminal
You now have a live terminal in your web app! Updates in real-time, handles keyboard/mouse events.
Available Themes
Choose from built-in themes:
:nord- Nord color scheme:dracula- Dracula theme:solarized_dark- Solarized Dark:solarized_light- Solarized Light:monokai- Monokai
15-Minute Tutorial: Interactive Terminal
Build a fully interactive REPL-style terminal.
The Plan
We'll create:
- Command input at the bottom
- Scrollable output area
- Command history (up/down arrows)
- Real-time updates
Full Implementation
defmodule MyAppWeb.InteractiveTerminalLive do
use MyAppWeb, :live_view
alias Raxol.Core.{Buffer, Box, Renderer}
@width 80
@height 24
@output_height 22
@input_height 2
def mount(_params, _session, socket) do
socket =
socket
|> assign(
buffer: create_initial_buffer(),
output_lines: ["Welcome to Interactive Terminal!", "Type 'help' for commands"],
input: "",
history: [],
history_index: 0
)
|> update_display()
{:ok, socket}
end
def render(assigns) do
~H"""
<div class="interactive-terminal">
<.live_component
module={Raxol.LiveView.TerminalComponent}
id="terminal"
buffer={@buffer}
theme={:nord}
on_keypress={fn key -> send(self(), {:key, key}) end}
/>
</div>
"""
end
def handle_info({:key, key}, socket) do
socket =
case key do
"Enter" ->
socket
|> execute_command()
|> clear_input()
"Backspace" ->
update(socket, :input, fn input ->
String.slice(input, 0..-2//1)
end)
"ArrowUp" ->
navigate_history(socket, :up)
"ArrowDown" ->
navigate_history(socket, :down)
char when byte_size(char) == 1 ->
update(socket, :input, fn input -> input <> char end)
_ ->
socket
end
|> update_display()
{:noreply, socket}
end
defp create_initial_buffer do
Buffer.create_blank_buffer(@width, @height)
|> Box.draw_box(0, 0, @width, @height, :double)
|> Box.draw_horizontal_line(0, @output_height, @width, "=")
end
defp update_display(socket) do
buffer = create_initial_buffer()
# Render output lines (last N lines that fit)
output_lines = Enum.take(socket.assigns.output_lines, -(@output_height - 2))
buffer =
output_lines
|> Enum.with_index()
|> Enum.reduce(buffer, fn {line, idx}, buf ->
Buffer.write_at(buf, 2, idx + 1, line, %{})
end)
# Render input line
buffer =
Buffer.write_at(
buffer,
2, @output_height + 1,
"> #{socket.assigns.input}",
%{fg_color: :cyan}
)
assign(socket, buffer: buffer)
end
defp execute_command(socket) do
input = String.trim(socket.assigns.input)
if input != "" do
output = process_command(input)
socket
|> update(:output_lines, fn lines ->
lines ++ ["> #{input}"] ++ output
end)
|> update(:history, fn hist -> [input | hist] end)
|> assign(history_index: 0)
else
socket
end
end
defp process_command("help") do
[
"Available commands:",
" help - Show this help",
" clear - Clear output",
" echo TEXT - Echo back text",
" time - Show current time",
" exit - Close terminal"
]
end
defp process_command("clear") do
# Clear handled separately
[]
end
defp process_command("echo " <> text) do
[text]
end
defp process_command("time") do
[DateTime.utc_now() |> to_string()]
end
defp process_command("exit") do
["Goodbye!"]
end
defp process_command(cmd) do
["Unknown command: #{cmd}. Type 'help' for available commands."]
end
defp clear_input(socket) do
assign(socket, input: "")
end
defp navigate_history(socket, direction) do
history = socket.assigns.history
index = socket.assigns.history_index
new_index =
case direction do
:up -> min(index + 1, length(history))
:down -> max(index - 1, 0)
end
input =
if new_index > 0 and new_index <= length(history) do
Enum.at(history, new_index - 1)
else
""
end
socket
|> assign(input: input)
|> assign(history_index: new_index)
end
endWhat You've Built
- Command input - Type commands at the bottom
- Command history - Up/Down arrows navigate history
- Scrollable output - Shows last 20 lines
- Real-time rendering - Instant visual updates
- Themed UI - Professional Nord theme
Try It
mix phx.server
# Visit http://localhost:4000/terminal
# Type: help
# Type: echo Hello World
# Type: time
# Press up arrow to recall commands
What's Next?
Add More Features
Syntax highlighting:
# Use Style module
style = Raxol.Core.Style.new(fg_color: :green, bold: true)
buffer = Buffer.write_at(buffer, x, y, "def function", style)Progress bars:
# Draw a progress bar
progress = 0.75 # 75%
width = 40
filled = round(width * progress)
buffer = Box.fill_area(buffer, 2, 10, filled, 1, "█", %{fg_color: :green})
buffer = Box.fill_area(buffer, 2 + filled, 10, width - filled, 1, "░", %{fg_color: :gray})Multiple panels:
# Split screen layout
buffer = Box.draw_box(buffer, 0, 0, 40, 24, :single) # Left panel
buffer = Box.draw_box(buffer, 40, 0, 40, 24, :single) # Right panelExplore More
- Core Concepts - Deep dive into buffers and rendering
- Migration Guide - Already have a terminal renderer?
- Cookbook - Practical patterns and recipes
- API Reference - Complete function documentation
Examples Directory
Check out working examples:
# Run examples
mix run examples/core/01_hello_buffer.exs
mix run examples/core/02_box_drawing.exs
mix run examples/liveview/01_simple_terminal/
Performance Targets
Raxol.Core is built for speed:
- Buffer operations: < 1ms for 80x24 grids
- Rendering: < 16ms (60fps capable)
- Memory: < 100KB per buffer
- Dependencies: Zero runtime dependencies
Get Help
- GitHub Issues: https://github.com/Hydepwns/raxol/issues
- Documentation: Browse
docs/directory - Examples: Working code in
examples/
Frequently Asked Questions
Do I need the full Raxol framework?
No! Start with raxol_core for just buffers, add raxol_liveview for web integration, or use raxol for everything.
Can I use this with my existing Phoenix app?
Yes! Raxol.LiveView integrates seamlessly with Phoenix LiveView.
Does this work in production?
Yes. Raxol is production-ready with 99%+ test coverage and performance benchmarks.
Can I customize the themes?
Yes! Either use built-in themes or create custom CSS. See Theming Cookbook.
What about mobile browsers?
Yes! The LiveView component is responsive and works on mobile (though keyboard input is limited to mobile keyboards).
Quick Reference
Buffer Operations
# Create
buffer = Buffer.create_blank_buffer(80, 24)
# Write
buffer = Buffer.write_at(buffer, x, y, "text", style)
# Read
cell = Buffer.get_cell(buffer, x, y)
# Clear
buffer = Buffer.clear(buffer)
# Resize
buffer = Buffer.resize(buffer, new_width, new_height)
# Render
output = Buffer.to_string(buffer)
diff = Renderer.render_diff(old_buffer, new_buffer)Box Drawing
# Styles: :single, :double, :rounded, :heavy, :dashed
buffer = Box.draw_box(buffer, x, y, width, height, :double)
# Lines
buffer = Box.draw_horizontal_line(buffer, x, y, length, "-")
buffer = Box.draw_vertical_line(buffer, x, y, length, "|")
# Fill
buffer = Box.fill_area(buffer, x, y, width, height, " ", style)Styles
# Create style
style = Style.new(
fg_color: :cyan,
bg_color: :black,
bold: true,
italic: false,
underline: false
)
# Merge styles
new_style = Style.merge(base_style, %{bold: true})
# Colors: :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white
# Or RGB: {255, 128, 0}
# Or 256-color: 42Ready to build? Pick a tutorial above and start coding!