What it is
Bubble Tea is a framework for building terminal user interfaces in Go, based on the Elm architecture of model, update and view.
Implement a Model with Init, Update and View. Update receives messages — key presses, window resizes, custom events — and returns a new model plus optional commands.
Installation
go get github.com/charmbracelet/bubbleteaGetting started
The smallest useful thing you can do with it, and what each part means.
go
type model struct {
choices []string
cursor int
selected map[int]bool
}
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 < len(m.choices)-1 { m.cursor++ }
case "enter", " ":
m.selected[m.cursor] = !m.selected[m.cursor]
}
}
return m, nil
}
func (m model) View() string {
s := "Select books:\n\n"
for i, choice := range m.choices {
cursor := " "
if m.cursor == i { cursor = ">" }
checked := " "
if m.selected[i] { checked = "x" }
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
}
return s + "\nq to quit\n"
}Advanced usage
Where the library earns its place over a simpler alternative.
go
type booksMsg []Book
func fetchBooks() tea.Cmd {
return func() tea.Msg {
books, err := api.List() // runs off the UI loop
if err != nil {
return errMsg{err}
}
return booksMsg(books)
}
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case booksMsg:
m.books = msg
m.loading = false
}
return m, nil
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- The interface freezes
- Update is doing blocking work. Move it into a tea.Cmd.
- Output is garbled after exit
- The program exited without restoring the terminal. Return the error from p.Run() and avoid os.Exit inside the model.
Best practices
- Never perform I/O directly in Update; return a tea.Cmd so the UI stays responsive.
- Always handle ctrl+c so the program can be interrupted.
- Handle tea.WindowSizeMsg to lay out correctly when the terminal is resized.
- Pair with Lip Gloss for styling rather than writing ANSI escape codes by hand.
Background
Why it exists, and what it was reacting to.
From Charm, Bubble Tea brought a principled state-management model to terminal UIs. Combined with Lip Gloss for styling, it is behind a wave of unusually polished Go CLI tools.
