Charm released v2 of its three flagship Go libraries on February 23, 2026—Bubble Tea (interaction framework), Lip Gloss (styling engine), and Bubbles (UI components)—marking the first breaking changes in the project’s 6-year history. The update delivers “orders of magnitude” performance improvements through a new Cursed Renderer based on ncurses algorithms, while fundamentally shifting the API from imperative commands to declarative View fields. With over 25,000 open-source projects depending on these libraries—including production tools from NVIDIA, Microsoft Azure, AWS, GitHub, and Slack—this release positions terminals as modern platforms for AI agents and developer tools.
Moreover, the v2 codebase powered Charm’s Crush AI coding agent in production for months before public release, battle-testing reliability under real-world constraints. Consequently, terminals aren’t legacy interfaces anymore—they’re experiencing a renaissance as primary platforms for AI agent interactions, and v2’s performance gains address this evolution directly.
Why Bubble Tea v2 Matters: 18,000+ Apps and Counting
Bubble Tea has become the dominant TUI framework in the Go ecosystem with 40.3k GitHub stars and over 18,000 applications built on it. The framework powers critical developer tools across major organizations: NVIDIA’s container-canary, Microsoft Azure’s Aztify (Terraform import tool), AWS’s eks-node-viewer, GitHub’s gh-dash, CockroachDB’s CLI, MinIO’s mc client, and Truffle Security’s credential scanner.
Furthermore, this production adoption at scale validates the framework’s architectural decisions. Bubble Tea follows The Elm Architecture pattern—a functional approach that cleanly separates state management (Model), event handling (Update), and rendering (View). As a result, developers from React or Redux backgrounds will find this familiar and intuitive.
Additionally, the v2 release represents Charm’s willingness to make breaking changes for better architecture, a maturity signal that respects its 25,000+ dependent projects while modernizing for the next 6 years.
Building Your First Bubble Tea v2 App
Bubble Tea applications follow three core components that compose into maintainable, testable terminal UIs. Here’s a complete counter app in 40 lines:
package main
import (
"fmt"
tea "charm.land/bubbletea/v2"
)
type model struct { count int }
func (m model) Init() (tea.Model, tea.Cmd) {
return m, nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
case "up":
m.count++
case "down":
if m.count > 0 {
m.count--
}
}
}
return m, nil
}
func (m model) View() tea.View {
s := fmt.Sprintf("Count: %d\n\nPress up/down to change, q to quit", m.count)
return tea.NewView(s)
}
func main() {
p := tea.NewProgram(model{})
if _, err := p.Run(); err != nil {
fmt.Printf("Error: %v", err)
}
}
The pattern breaks down cleanly: Model holds state (a simple integer here), Init() returns initial state and commands, Update() handles events and returns a new model plus commands to run, and View() renders UI based on current state. Importantly, this separation makes each component independently testable—Update() is a pure function, View() has no side effects.
The Elm Architecture’s message-passing approach scales from simple counters to complex applications like GitHub’s gh-dash or AWS’s eks-node-viewer. Commands (tea.Cmd) handle async operations like network requests or timers, returning messages that flow back through Update(). Consequently, this event-driven runtime prevents blocking the UI thread.
The Declarative API Shift: Single Source of Truth
v2’s biggest conceptual change moves configuration from scattered imperative commands to declarative View fields set in one location. Instead of calling startup options and runtime toggles across your codebase, you now declare what you want in View() and Bubble Tea handles the rest.
The before and after comparison shows the paradigm shift:
// v1: Imperative commands scattered across code
p := tea.NewProgram(
model{},
tea.WithAltScreen(),
tea.WithMouseCellMotion(),
)
// Later in Update(): return m, tea.EnterAltScreen
// v2: Declarative fields in one place
func (m model) View() tea.View {
v := tea.NewView(m.renderContent())
v.AltScreen = true
v.MouseMode = tea.MouseModeCellMotion
return v
}
However, this isn’t just cleaner syntax—it eliminates state conflicts. In v1, startup options and runtime commands could fight over features like alt-screen mode or mouse handling. In contrast, v2 makes View() the single source of truth. Want full-screen mode? Set v.AltScreen = true. Want to disable it conditionally? Set it to false based on model state. No more tracking toggle state across Init(), Update(), and scattered commands.
Moreover, the declarative approach aligns with broader industry trends: React Hooks replaced lifecycle methods, SwiftUI replaced UIKit’s imperative API, and now Bubble Tea v2 replaces scattered configuration with unified View declarations. This maturity shows in the architecture—Charm broke compatibility to get it right.
Cursed Renderer: Performance That Actually Matters
The new Cursed Renderer delivers “orders of magnitude” faster terminal rendering compared to v1 by implementing algorithms borrowed from ncurses. For local applications, this means snappier UI updates. However, for SSH applications—where rendering efficiency directly impacts bandwidth costs—it translates to measurable cost reductions in cloud environments.
Furthermore, the performance claims come with production proof. Charm ran v2 in Crush, their AI coding agent, for months before release. That battle-testing matters more than benchmarks: real-world constraints like long-running terminal sessions, complex UI updates, and SSH latency validated the renderer under production load before anyone outside Charm touched the code.
Additionally, v2 adds support for modern terminal capabilities: inline image rendering, synchronized rendering (flicker-free updates), clipboard transfer over SSH connections, and richer keyboard input handling. The new colorprofile library auto-detects terminal color capabilities and downsamples ANSI styling to the best available profile, meaning colors “just work” regardless of terminal limitations.
These aren’t nice-to-haves—they’re table stakes as terminals evolve from legacy text interfaces to primary platforms for AI agents. Crush demonstrates this future: an AI coding agent needs responsive rendering, reliable clipboard operations over SSH, and support for rich output like images and charts. Consequently, v2 delivers those capabilities at the framework level.
Migration Path: Breaking Changes With Comprehensive Guides
v2 introduces breaking changes that affect all 25,000+ dependent projects. Import paths changed to vanity domains (charm.land/bubbletea/v2), View() now returns a tea.View struct instead of a string, keyboard/mouse APIs were restructured, and all imperative commands were replaced with declarative View fields.
Key migrations include: updating imports from github.com/charmbracelet/bubbletea to charm.land/bubbletea/v2, changing View signatures from func View() string to func View() tea.View, switching from tea.KeyMsg to tea.KeyPressMsg with renamed fields, and replacing the space bar string " " with the named key "space".
However, the changes are mechanical, not conceptual—you’re not relearning the architecture, just updating syntax. Charm provides comprehensive upgrade guides for both human developers and LLMs, with before/after examples for every breaking change. For actively developed projects, the migration effort pays off in cleaner code and significant performance gains. For stable applications, evaluate based on whether you need v2’s modern features and performance improvements.
Importantly, new projects should start with v2. The declarative API is more intuitive, the performance is substantially better, and the modern terminal support positions you for emerging use cases like AI agent interfaces.
Key Takeaways
Bubble Tea v2 represents a fundamental rethinking of terminal UI development in Go, validated by months of production use in Charm’s Crush AI agent before public release. The key improvements:
- Performance that scales: Cursed Renderer delivers “orders of magnitude” faster rendering, particularly critical for SSH applications where bandwidth efficiency impacts costs
- Cleaner architecture: Declarative View fields replace scattered imperative commands, creating a single source of truth that eliminates state conflicts
- Modern terminal support: Inline images, synchronized rendering, clipboard over SSH, and auto color downsampling position terminals as viable platforms for AI agents
- Production-proven reliability: 18,000+ existing applications, adoption by NVIDIA/Azure/AWS/GitHub, and months of Crush battle-testing validate architectural decisions
- Responsible breaking changes: Comprehensive migration guides (human and LLM versions) with mechanical syntax updates, not conceptual rewrites
The v2 release signals Bubble Tea’s maturity: willing to break compatibility for better fundamentals, backed by thorough migration documentation and production validation. For Go developers building CLI tools or AI agent interfaces, Bubble Tea v2 sets the standard for modern terminal UIs.

