cloudparts

Bubble Tea

A Go framework from Charm for terminal apps built on the Elm Architecture: one model holds the state, Update turns messages into a new model, and View describes the screen.

Type
Open source
Made by
Charmbracelet, Inc.
License
MIT
Facts checked
13 October 2026

What it is

A Bubble Tea program is a model plus three methods. Init returns an optional first command. Update receives a message (a key press, a mouse click, a window resize, a timer tick, a server response), returns the updated model and optionally a command. View turns the model into a tea.View, and the runtime redraws after every update, so the app has no redrawing logic of its own. Commands (tea.Cmd) are functions that do I/O; the runtime runs each one in its own goroutine and feeds the message it returns back into Update. tea.Batch runs several at once with no ordering guarantees, tea.Sequence one after another.

Version 2, released in February 2026, made the view declarative: instead of commands such as tea.EnterAltScreen, View sets fields like AltScreen, MouseMode and the window title, and the runtime applies them. Its “Cursed Renderer”, modeled on the ncurses rendering algorithm, redraws at most 60 times a second by default (configurable up to 120); Charm says it cuts bandwidth by orders of magnitude for apps served over SSH with its Wish library. It reads the Kitty keyboard protocol where the terminal supports it, so apps can bind keys like shift+enter and see key releases, and it downsamples colors to what the terminal can show. Apps can run full-window in the alternate screen or inline below the prompt.

Bubble Tea is the interaction layer of a set of Charm libraries: Bubbles provides components such as text inputs, viewports and spinners, and Lip Gloss handles styling and layout. Charmbracelet, Inc. is an active, venture-funded company: it shipped v2.1.0 in October 2026 and builds its own AI coding agent, Crush, on these libraries.

Why teams pick it

  • One model and one Update function make state changes predictable and easy to test: every change comes from a message
  • Concurrency without shared state: commands run in goroutines and report back as messages, so I/O never blocks the UI
  • A family of libraries (Bubbles components, Lip Gloss styling, Wish for SSH apps) and, per its README, more than 18,000 applications built on it, including tools from AWS, NVIDIA and Microsoft Azure
  • v2 handles modern terminal features for you: keyboard enhancements, color downsampling, synchronized rendering and clipboard transfer over SSH

Watch out for

  • v2 broke the API: the import path moved to charm.land/bubbletea/v2, View returns a tea.View instead of a string, and key and mouse messages were split into new types, so v1 code needs the upgrade guide
  • The app owns stdin and stdout, so you can’t print-debug: logs go to a file (tea.LogToFile) and Delve must run in headless mode
  • Commands started with tea.Batch run concurrently with no ordering guarantees, so their messages can arrive in any order; use tea.Sequence when order matters
  • Richer key handling only works in terminals that support progressive keyboard enhancement (Ghostty, Kitty, WezTerm, iTerm2 and others); apps need fallbacks elsewhere

Alternatives

What all terminal user interfaces (tuis) have in common →Compare side by side →

How it fits with other technologies

A countdown that ticks with a command (Bubble Tea v2) · go

package main

import (
	"fmt"
	"os"
	"time"

	tea "charm.land/bubbletea/v2"
)

type tickMsg time.Time

// A command: the runtime runs it in a goroutine and sends its result to Update.
func tick() tea.Cmd {
	return tea.Tick(time.Second, func(t time.Time) tea.Msg { return tickMsg(t) })
}

type model struct{ left int }

func (m model) Init() tea.Cmd { return tick() }

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyPressMsg:
		if msg.String() == "q" || msg.String() == "ctrl+c" {
			return m, tea.Quit
		}
	case tickMsg:
		m.left--
		if m.left == 0 {
			return m, tea.Quit
		}
		return m, tick()
	}
	return m, nil
}

func (m model) View() tea.View {
	return tea.NewView(fmt.Sprintf("Quitting in %d seconds. Press q to quit now.\n", m.left))
}

func main() {
	if _, err := tea.NewProgram(model{left: 5}).Run(); err != nil {
		fmt.Println("error:", err)
		os.Exit(1)
	}
}

See it in a real stack

No architecture uses Bubble Tea yet. Drop it into the playground and wire it up yourself.Open playground →

Worth exploring

WebsiteAwesome TUIsJustin Garrison and contributors, on GitHubA community-maintained list of more than 700 terminal apps and libraries in 13 sections, from dashboards, editors and Docker or Kubernetes front ends to games, plus TUI libraries grouped by language (Python, Go, C, C++, Java, .NET, Rust and others). Entries must be interactive, maintained and not just wrappers around tools like fzf; they arrive by pull request with one-line descriptions, and the maintainer says to run them at your own risk.
All resources →
Where it came from2019–2026

Charm started in 2019 as a group of friends from consumer tech (Apple, Last.fm, TweetDeck) who wanted to bring modern product design to the command line. Their first projects, Glow and Glamour, styled Markdown in the terminal, but they also needed a way to build interactive interfaces. Christian Rocha, who came from Haskell and Elm, wanted to write stateful text interfaces without thinking about rendering, and found nothing in the Go ecosystem that did it, so the team built Bubble Tea on the Elm Architecture. Its first tagged release, v0.1.0, came out in April 2020.

Bubble Tea grew with its sister libraries, Bubbles and Lip Gloss, and with Charm’s own tools built on them, such as Glow. By November 2023, when Charm raised $6 million in a round led by Gradient, Google’s AI-focused fund, it powered more than 4,000 apps. Charm tagged v1.0.0 in August 2024 as an “honorary release” to mark the API as stable.

When AI coding agents moved into the terminal, Charm decided the libraries had to carry more weight. Bubble Tea v2, released with Lip Gloss and Bubbles v2 on 23 February 2026, brought the new Cursed Renderer, richer keyboard input and a declarative view; the v2 branches had run Charm’s coding agent Crush in production for months first. Charm says it had never pushed a breaking change before v2.

Why it’s built this way

The Elm Architecture in Go
A single model updated only by messages lets developers build stateful, animated interfaces without managing redraws, the experience Charm’s founders knew from building browser UIs in Elm.
Commands instead of user-managed goroutines
All I/O goes through tea.Cmd functions that the runtime runs concurrently and turns into messages, so many asynchronous operations stay simple and the model is only touched in Update.
Inline mode as a first-class use case, compiled to native code
Charm wanted interfaces that can appear inline in a normal terminal session, not only full-screen apps, and sees speed as one of the terminal’s main advantages, locally and over SSH.
A declarative view and one owner of terminal I/O in v2
In v1, Bubble Tea and Lip Gloss could fight over the terminal (one reading keys, the other querying the background color), and terminal modes were toggled with commands. In v2 Bubble Tea owns all I/O and the view simply declares what it wants.

Timeline

  1. 2019

    Charm founded to build tools for the command line

  2. 2020

    Bubble Tea v0.1.0 tagged (April)

  3. 2023

    Charm raises $6M led by Gradient; Bubble Tea powers over 4,000 apps (November)

  4. 2024

    v1.0.0 marks the API as stable (August)

  5. 2026

    v2.0.0 with the Cursed Renderer and the charm.land import path (February)

  6. 2026

    v2.1.0 adds the OSC 7501 program status protocol (October)

Sources7 · facts checked 13 October 2026

Improve this page on GitHub