Terminal UI: BubbleTea (Go) vs Ratatui (Rust)
Source: Dev.to
BubbleTea (Go)
What is BubbleTea?
BubbleTea is a Go framework for TUIs based on The Elm Architecture. You describe your app with a model (state) and three pieces:
| Piece | Purpose |
|---|---|
| Init | Returns an initial command (or nil). |
| Update | Handles incoming messages, returns a new model and an optional command. |
| View | Renders the UI as a string. |
The framework runs the event loop, turns key‑presses and I/O into messages, and redraws the UI whenever the model changes. In short, it’s the fun, stateful way to build terminal apps in Go, with a single source of truth and predictable updates.
Why choose BubbleTea?
- Production‑ready (v1.x) with tens of thousands of GitHub stars.
- Works inline, full‑screen, or mixed.
- Usually paired with Bubbles (components such as inputs, viewports, spinners) and Lip Gloss (styling).
- Fits naturally into a typical Go project layout (
cmd/, packages, etc.).
Minimal BubbleTea program
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
)
type model struct {
choices []string
cursor int
selected map[int]struct{}
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
case "down", "j":
if m.cursor "
}
checked := " "
if _, ok := m.selected[i]; ok {
checked = "x"
}
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
}
return s + "\nPress q to quit.\n"
}
func main() {
if _, err := tea.NewProgram(model{
choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
selected: make(map[int]struct{}),
}).Run(); err != nil {
fmt.Printf("error: %v", err)
os.Exit(1)
}
}
Notable apps built with BubbleTea
- Crush – Charm’s TUI‑based AI coding agent.
- Glow – Markdown viewer.
- Huh – Interactive prompts.
- Many other tools from the top‑trending Go projects ecosystem.
For larger Go applications you can add dependency injection, solid unit tests, and other Go‑idiomatic patterns; the same ideas apply to BubbleTea models and commands.
Ratatui (Rust)
What is Ratatui?
Ratatui is a Rust library for TUIs that uses immediate‑mode rendering: each frame you describe the entire UI (widgets and layout), and Ratatui draws it.
- It is lightweight and unopinionated – it does not impose an Elm‑style model or a specific app structure.
- You keep your own state, run your own event loop (typically with crossterm, termion, or termwiz), and call
terminal.draw(|f| { … })to render.
Elm‑style vs. Immediate‑mode
| Approach | Who owns the loop? | How UI is described |
|---|---|---|
| Elm‑style (BubbleTea) | Framework | Update → View (only changed parts are redrawn) |
| Immediate‑mode (Ratatui) | You (the app) | Every frame you rebuild the whole UI from current state |
Why choose Ratatui?
- Used by 2,100+ crates and trusted by companies such as Netflix (e.g.
bpftop), OpenAI, AWS (e.g.amazon-q-developer-cli), and Vercel. - Current stable version is 0.30.x with strong documentation and optional back‑ends.
- Ideal when you need full control over input handling and rendering, or when you’re already working in the Rust ecosystem.
Minimal Ratatui program
use crossterm::event::{self, Event, KeyCode};
use ratatui::{prelude::*, widgets::Paragraph};
use std::time::Duration;
fn main() -> Result<()> {
// Initialise the terminal
let mut terminal = ratatui::init();
loop {
// Draw the UI each frame
terminal.draw(|frame| {
let area = frame.area();
frame.render_widget(
Paragraph::new("Hello, Ratatui! Press q to quit.")
.alignment(Alignment::Center),
area,
);
})?;
// Handle input (poll every 250 ms)
if event::poll(Duration::from_millis(250))? {
if let Event::Key(key) = event::read()? {
if key.code == KeyCode::Char('q') {
break;
}
}
}
}
// Restore the terminal state before exiting
ratatui::restore();
Ok(())
}
When to Choose Which?
| Aspect | BubbleTea (Go) | Ratatui (Rust) |
|---|---|---|
| Architecture | Elm Architecture (model + Init/Update/View) | Immediate mode (you own the loop) |
| Ecosystem | Bubbles, Lip Gloss, 10k+ apps (e.g., Crush) | 2,100+ crates, used by Netflix, OpenAI, AWS, Vercel |
| Best for | Rapid iteration, Go teams, CLIs | Full control, performance‑sensitive TUIs, Rust codebases |
| Typical use‑case | Want the fastest path to a polished TUI in Go | Need maximum control or already in Rust |
Bottom line: Both frameworks are excellent. The preferred language and the amount of structure you want from the framework should drive the decision.
Further Reading & Resources
- Top 19 Trending Go Projects on GitHub – January 2026
- Top 23 Trending Rust Projects on GitHub – January 2026
- Dependency Injection in Go: Patterns & Best Practices
Crush UI (screenshot above) is implemented using the BubbleTea framework.
Practices
- Go SDKs for Ollama – comparison with examples
- Comparing Go ORMs for PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Go Project Structure: Practices & Patterns
- Go Unit Testing: Structure & Best Practices
- Bubble Tea – The Elm Architecture for Go TUIs
- Ratatui – Introduction
- Ratatui – Rendering (immediate mode)
- Charm Crush – TUI coding agent
- Ratatui on crates.io