DEV Community

Alex Spinov
Alex Spinov

Posted on

Bubbletea Has a Free API: The Go Framework for Building Beautiful Terminal UIs With the Elm Architecture

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
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)