Terminal apps do not have to look like it is 1985. Bubbletea brings the Elm architecture to Go terminal applications — build interactive, beautiful TUI apps with a clean Model-Update-View pattern.
What Is Bubbletea?
Bubbletea is a Go framework for building terminal user interfaces. It uses the Elm architecture (Model-Update-View) for predictable state management. Combined with its companion libraries Lipgloss (styling), Bubbles (components), and Glamour (markdown), you can build terminal apps that look and feel modern.
The Free Framework
The Charm ecosystem is completely free and open source:
- Bubbletea: Core TUI framework (Elm architecture)
- Lipgloss: CSS-like terminal styling
- Bubbles: Pre-built components (tables, spinners, text inputs)
- Glamour: Markdown rendering in terminal
- Wish: SSH server for TUI apps
- Log: Beautiful structured logging
Quick Start
go get github.com/charmbracelet/bubbletea
go get github.com/charmbracelet/lipgloss
go get github.com/charmbracelet/bubbles
Build a simple todo app:
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var selectedStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF75B7")).
Bold(true)
type model struct {
choices []string
cursor int
selected map[int]struct{}
}
func initialModel() model {
return model{
choices: []string{"Buy groceries", "Write code", "Read a book", "Go for a walk"},
selected: make(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", "ctrl+c":
return m, tea.Quit
case "up", "k":
if m.cursor > 0 { m.cursor-- }
case "down", "j":
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 := "Todo List:\n\n"
for i, choice := range m.choices {
cursor := " "
if m.cursor == i { cursor = ">" }
checked := " "
if _, ok := m.selected[i]; ok { checked = "x" }
line := fmt.Sprintf("%s [%s] %s", cursor, checked, choice)
if m.cursor == i {
s += selectedStyle.Render(line) + "\n"
} else {
s += line + "\n"
}
}
s += "\nPress q to quit.\n"
return s
}
func main() {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
fmt.Printf("Error: %v", err)
}
}
Why Developers Choose Bubbletea
The creators of Charm built their entire company toolchain as terminal apps: a file manager, a git UI, a markdown viewer, and an SSH-based app platform. All built with Bubbletea. The Elm architecture made each app predictable and testable, while Lipgloss made them beautiful.
Who Is This For?
- Go developers building CLI tools and terminal applications
- DevOps engineers creating interactive deployment tools
- Developers wanting to build terminal dashboards and monitors
- Anyone who thinks terminal apps should look beautiful
Start Building TUIs
Bubbletea proves terminal apps can be beautiful, interactive, and fun to build.
Need help building CLI tools or terminal applications? I build custom developer tools — reach out to discuss your project.
Found this useful? I publish daily deep-dives into developer tools and APIs. Follow for more.
Top comments (0)