We don’t take API changes lightly and strive to make the upgrade process as simple as possible. If something feels way off, let us know.
Migration Checklist
Here’s the short version — a checklist you can follow top to bottom. Each item links to the relevant section below.Import Paths
The module path changed to a vanity domain. Lip Gloss moved too.Before
After
The Big Idea: Declarative Views
In v1, you’d use program options liketea.WithAltScreen() and commands like tea.EnterAltScreen to toggle terminal features on and off. In v2, you just set fields on the tea.View struct in your View() method and Bubble Tea handles the rest.
This means: no more startup option flags, no more toggle commands, no more fighting over state. Just declare what you want and Bubble Tea will make it so.
v1: imperative — scattered across NewProgram, Init, and Update
v2: declarative — everything lives in View()
Keep this in mind as you go through the rest of the guide — most of the “removed” things simply moved into View fields.
View Returns a tea.View Now
The View() method no longer returns a string. It returns a tea.View struct.
Before
After
Extended form
Available View Fields
Thetea.View struct has fields for everything that used to be controlled by options and commands:
| View Field | What It Does |
|---|---|
Content | The rendered string (set via SetContent() or NewView()) |
AltScreen | Enter/exit the alternate screen buffer |
MouseMode | MouseModeNone, MouseModeCellMotion, or MouseModeAllMotion |
ReportFocus | Enable focus/blur event reporting |
DisableBracketedPasteMode | Disable bracketed paste |
WindowTitle | Set the terminal window title |
Cursor | Control cursor position, shape, color, and blink |
ForegroundColor | Set the terminal foreground color |
BackgroundColor | Set the terminal background color |
ProgressBar | Show a native terminal progress bar |
KeyboardEnhancements | Request keyboard enhancement features |
OnMouse | Intercept mouse messages based on view content |
Key Messages
Key messages got a major overhaul. Here’s the quick rundown:tea.KeyMsg is now an interface
For most code, you want tea.KeyPressMsg:
Before
After
tea.KeyMsg and type-switch inside:
Handling both presses and releases
Key fields changed
| v1 | v2 | Notes |
|---|---|---|
msg.Type | msg.Code | A rune — can be tea.KeyEnter, 'a', etc. |
msg.Runes | msg.Text | Now a string, not []rune |
msg.Alt | msg.Mod | msg.Mod.Contains(tea.ModAlt) for alt, etc. |
tea.KeyRune | — | Check len(msg.Text) > 0 instead |
tea.KeyCtrlC | — | Use msg.String() == "ctrl+c" or check msg.Code + msg.Mod |
Space bar changed
Space bar now returns"space" instead of " " when using msg.String():
Before
After
key.Code is still ' ' and key.Text is still " ", but String() returns "space".Ctrl+key matching
Before
After (option A — string matching)
After (option B — field matching)
New Key fields
These are new in v2 and don’t have v1 equivalents:key.ShiftedCode— the shifted key code (e.g.,'B'when pressing shift+b)key.BaseCode— the key on a US PC-101 layout (handy for international keyboards)key.IsRepeat— whether the key is auto-repeating (Kitty protocol / Windows Console only)key.Keystroke()— likeString()but always includes modifier info
Paste Messages
Paste events no longer come in astea.KeyMsg with a Paste flag. They’re now their own message types:
Before
After
Mouse Messages
tea.MouseMsg is now an interface
You get the coordinates by calling msg.Mouse():
Before
After
Mouse events are split by type
Instead of checkingmsg.Action, match on specific message types:
Before
After
Button constants renamed
| v1 | v2 |
|---|---|
tea.MouseButtonLeft | tea.MouseLeft |
tea.MouseButtonRight | tea.MouseRight |
tea.MouseButtonMiddle | tea.MouseMiddle |
tea.MouseButtonWheelUp | tea.MouseWheelUp |
tea.MouseButtonWheelDown | tea.MouseWheelDown |
tea.MouseButtonWheelLeft | tea.MouseWheelLeft |
tea.MouseButtonWheelRight | tea.MouseWheelRight |
tea.MouseEvent → tea.Mouse
The MouseEvent struct is gone. The new Mouse struct has X, Y, Button, and Mod fields.
Mouse mode is now a View field
Before
After
Removed Program Options
| Removed Option | Do This Instead |
|---|---|
tea.WithAltScreen() | view.AltScreen = true |
tea.WithMouseCellMotion() | view.MouseMode = tea.MouseModeCellMotion |
tea.WithMouseAllMotion() | view.MouseMode = tea.MouseModeAllMotion |
tea.WithReportFocus() | view.ReportFocus = true |
tea.WithoutBracketedPaste() | view.DisableBracketedPasteMode = true |
tea.WithInputTTY() | Just remove it — v2 always opens the TTY for input automatically |
tea.WithANSICompressor() | Just remove it — the new renderer handles optimization automatically |
Removed Commands
| Removed Command | Do This Instead |
|---|---|
tea.EnterAltScreen | view.AltScreen = true |
tea.ExitAltScreen | view.AltScreen = false |
tea.EnableMouseCellMotion | view.MouseMode = tea.MouseModeCellMotion |
tea.EnableMouseAllMotion | view.MouseMode = tea.MouseModeAllMotion |
tea.DisableMouse | view.MouseMode = tea.MouseModeNone |
tea.HideCursor | view.Cursor = nil |
tea.ShowCursor | view.Cursor = &tea.Cursor{...} or tea.NewCursor(x, y) |
tea.EnableBracketedPaste | view.DisableBracketedPasteMode = false |
tea.DisableBracketedPaste | view.DisableBracketedPasteMode = true |
tea.EnableReportFocus | view.ReportFocus = true |
tea.DisableReportFocus | view.ReportFocus = false |
tea.SetWindowTitle("...") | view.WindowTitle = "..." |
Removed Program Methods
| Removed Method | Do This Instead |
|---|---|
p.Start() | p.Run() |
p.StartReturningModel() | p.Run() |
p.EnterAltScreen() | view.AltScreen = true in View() |
p.ExitAltScreen() | view.AltScreen = false in View() |
p.EnableMouseCellMotion() | view.MouseMode in View() |
p.DisableMouseCellMotion() | view.MouseMode = tea.MouseModeNone in View() |
p.EnableMouseAllMotion() | view.MouseMode in View() |
p.DisableMouseAllMotion() | view.MouseMode = tea.MouseModeNone in View() |
p.SetWindowTitle(...) | view.WindowTitle in View() |
Renamed APIs
| v1 | v2 | Notes |
|---|---|---|
tea.Sequentially(...) | tea.Sequence(...) | Sequentially was already deprecated in v1 |
tea.WindowSize() | tea.RequestWindowSize | Now returns Msg directly, not a Cmd |
New Program Options
These are new in v2:| Option | What It Does |
|---|---|
tea.WithColorProfile(p) | Force a specific color profile (great for testing) |
tea.WithWindowSize(w, h) | Set initial terminal size (great for testing) |
Complete Before & After
Here’s a minimal but complete program showing the most common migration patterns side by side.v1 Example
v1
v2 Example
v2
Notice how the
NewProgram call got simpler? All the terminal feature flags moved into View() where they belong.Quick Reference
A flat old → new lookup table. Handy for search-and-replace and LLM-assisted migration.Import Paths
| v1 | v2 |
|---|---|
github.com/charmbracelet/bubbletea | charm.land/bubbletea/v2 |
github.com/charmbracelet/lipgloss | charm.land/lipgloss/v2 |
Model Interface
| v1 | v2 |
|---|---|
View() string | View() tea.View |
Key Events
| v1 | v2 |
|---|---|
tea.KeyMsg (struct) | tea.KeyPressMsg for presses, tea.KeyMsg (interface) for both |
msg.Type | msg.Code |
msg.Runes | msg.Text (string, not []rune) |
msg.Alt | msg.Mod.Contains(tea.ModAlt) |
tea.KeyRune | check len(msg.Text) > 0 |
tea.KeyCtrlC | msg.Code == 'c' && msg.Mod == tea.ModCtrl or msg.String() == "ctrl+c" |
case " ": (space) | case "space": |
Mouse Events
| v1 | v2 |
|---|---|
tea.MouseMsg (struct) | tea.MouseMsg (interface) — call .Mouse() for the data |
tea.MouseEvent | tea.Mouse |
tea.MouseButtonLeft | tea.MouseLeft |
tea.MouseButtonRight | tea.MouseRight |
tea.MouseButtonMiddle | tea.MouseMiddle |
tea.MouseButtonWheelUp | tea.MouseWheelUp |
tea.MouseButtonWheelDown | tea.MouseWheelDown |
msg.X, msg.Y (direct) | msg.Mouse().X, msg.Mouse().Y |
Options → View Fields
| v1 Option | v2 View Field |
|---|---|
tea.WithAltScreen() | view.AltScreen = true |
tea.WithMouseCellMotion() | view.MouseMode = tea.MouseModeCellMotion |
tea.WithMouseAllMotion() | view.MouseMode = tea.MouseModeAllMotion |
tea.WithReportFocus() | view.ReportFocus = true |
tea.WithoutBracketedPaste() | view.DisableBracketedPasteMode = true |
Commands → View Fields
| v1 Command | v2 View Field |
|---|---|
tea.EnterAltScreen / tea.ExitAltScreen | view.AltScreen = true/false |
tea.EnableMouseCellMotion | view.MouseMode = tea.MouseModeCellMotion |
tea.EnableMouseAllMotion | view.MouseMode = tea.MouseModeAllMotion |
tea.DisableMouse | view.MouseMode = tea.MouseModeNone |
tea.HideCursor / tea.ShowCursor | view.Cursor = nil / view.Cursor = &tea.Cursor{...} |
tea.EnableBracketedPaste / tea.DisableBracketedPaste | view.DisableBracketedPasteMode = false/true |
tea.EnableReportFocus / tea.DisableReportFocus | view.ReportFocus = true/false |
tea.SetWindowTitle("...") | view.WindowTitle = "..." |
Removed Options (No Replacement Needed)
| v1 Option | What Happened |
|---|---|
tea.WithInputTTY() | v2 always opens the TTY for input automatically |
tea.WithANSICompressor() | The new renderer handles optimization automatically |
Removed Program Methods
| v1 Method | v2 Replacement |
|---|---|
p.Start() | p.Run() |
p.StartReturningModel() | p.Run() |
p.EnterAltScreen() | view.AltScreen = true in View() |
p.ExitAltScreen() | view.AltScreen = false in View() |
p.EnableMouseCellMotion() | view.MouseMode in View() |
p.DisableMouseCellMotion() | view.MouseMode = tea.MouseModeNone in View() |
p.EnableMouseAllMotion() | view.MouseMode in View() |
p.DisableMouseAllMotion() | view.MouseMode = tea.MouseModeNone in View() |
p.SetWindowTitle(...) | view.WindowTitle in View() |
Other Renames
| v1 | v2 |
|---|---|
tea.Sequentially(...) | tea.Sequence(...) |
tea.WindowSize() | tea.RequestWindowSize (now returns Msg, not Cmd) |
New Program Options
| Option | Description |
|---|---|
tea.WithColorProfile(p) | Force a specific color profile |
tea.WithWindowSize(w, h) | Set initial window size (great for testing) |