Bubble Tea — Powerful TUI Framework for Go
Bubble Tea is a powerful little TUI framework for Go based on The Elm Architecture. Build terminal applications with composable components, events, and commands. Part of the Charm ecosystem (Lip Gloss, Gum, Wish, Huh). Powers lazygit, soft-serve, and many CLI tools.
What it is
Bubble Tea is a terminal user interface (TUI) framework for Go, built on the Elm Architecture pattern. You define a model (state), an update function (handles messages), and a view function (renders the UI). The framework handles the event loop, terminal rendering, and input management.
Bubble Tea is part of the Charm ecosystem, which includes Lip Gloss (styling), Bubbles (pre-built components), Gum (shell scripting), and Wish (SSH apps). It powers tools like lazygit, soft-serve, and many CLI applications.
How it saves time or tokens
Bubble Tea provides a structured architecture for TUI development. Without it, building interactive terminal apps involves raw ANSI escape codes, manual input handling, and custom rendering loops. Bubble Tea abstracts these concerns into a clean Model-Update-View cycle.
The Bubbles component library provides pre-built widgets (text inputs, spinners, lists, tables, file pickers) that you compose into complex UIs without writing rendering logic from scratch.
How to use
- Add Bubble Tea to your Go project:
go get github.com/charmbracelet/bubbletea
- Define a model, update, and view:
package main
import (
"fmt"
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 "q":
return m, tea.Quit
case "up":
if m.cursor > 0 { m.cursor-- }
case "down":
if m.cursor < len(m.choices)-1 { m.cursor++ }
case "enter":
if _, ok := m.selected[m.cursor]; ok {
delete(m.selected, m.cursor)
} else {
m.selected[m.cursor] = struct{}{}
}
}
}
return m, nil
}
func (m model) View() string {
s := "Pick items:\n\n"
for i, choice := range m.choices {
cursor := " "
if m.cursor == i { 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"
}
- Run the program:
func main() {
p := tea.NewProgram(model{
choices: []string{"Option A", "Option B", "Option C"},
selected: make(map[int]struct{}),
})
p.Run()
}
Example
Using Lip Gloss for styled output:
import "github.com/charmbracelet/lipgloss"
var style = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("205")).
Border(lipgloss.RoundedBorder())
func (m model) View() string {
return style.Render("Styled TUI content")
}
Related on TokRepo
- AI tools for coding -- Development frameworks and libraries
- Automation tools -- CLI and terminal tools
Common pitfalls
- Mutating the model directly instead of returning a new model from Update. The Elm Architecture expects immutable updates. Always return a new or modified model from the Update function.
- Blocking in the Update function. Long-running operations should be dispatched as Cmds (async functions). Blocking the Update function freezes the UI.
- Not using the Bubbles component library. Before building custom widgets, check if Bubbles already has what you need (text input, spinner, progress bar, viewport, table).
Frequently Asked Questions
The Elm Architecture is a pattern from the Elm programming language: Model (state) + Update (state transitions) + View (rendering). It makes state management predictable and testable. Bubble Tea adopts this pattern for terminal UIs because it cleanly separates logic from presentation.
Yes, via Lip Gloss, the styling library in the Charm ecosystem. Lip Gloss provides CSS-like styling for terminal output: colors, borders, padding, alignment. It handles terminal color capability detection automatically.
Bubbles is a companion library of pre-built Bubble Tea components: text inputs, spinners, lists, tables, file pickers, progress bars, and paginated views. Each component implements the Bubble Tea Model interface and can be composed into larger UIs.
Yes. Wish is a Charm library that serves Bubble Tea apps over SSH. Users connect via 'ssh myapp.example.com' and interact with the TUI in their terminal. This is how soft-serve (Git server) works.
tview provides a higher-level widget-based API (forms, tables, pages). Bubble Tea provides a lower-level architecture (Elm pattern) with more control. Bubble Tea is more flexible for custom UIs; tview is faster for standard form-based interfaces.
Citations (3)
- Bubble Tea GitHub— Bubble Tea is a TUI framework for Go based on the Elm Architecture
- Charm Website— Charm ecosystem: Lip Gloss, Bubbles, Gum, Wish
- Elm Architecture Guide— The Elm Architecture pattern for UI development
Related on TokRepo
Discussion
Related Assets
Conda — Cross-Platform Package and Environment Manager
Install, update, and manage packages and isolated environments for Python, R, C/C++, and hundreds of other languages from a single tool.
Sphinx — Python Documentation Generator
Generate professional documentation from reStructuredText and Markdown with cross-references, API autodoc, and multiple output formats.
Neutralinojs — Lightweight Cross-Platform Desktop Apps
Build desktop applications with HTML, CSS, and JavaScript using a tiny native runtime instead of bundling Chromium.