# Bubble Tea v2 moved its import path to charm.land and changed what View returns

> Bubble Tea is a Go terminal framework built on the Elm Architecture, and its v2 line is a different module from v1: the import is charm.land/bubbletea/v2, View returns a tea.View value instead of a string, and a beta was retracted in go.mod. The renderer, termios handling and terminal features are all in the library, and the components are not.

**charmbracelet/bubbletea** — GitHub describes it as A powerful little TUI framework 🏗. The repository metadata lists Go as its primary language. The metadata lists the MIT license. This article stays within the project description and details documented in the GitHub repository README.

- Repository: https://github.com/charmbracelet/bubbletea
- Stars: 45,200 · Forks: 2,754
- Language: Go
- License: MIT
- Published: 2026-08-13 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/charmbracelet-bubbletea

## The module path is charm.land/bubbletea/v2, not a github.com path

The first line of go.mod is the whole migration story:

```
module charm.land/bubbletea/v2
```

That is a different module from the v1 line, and the tutorial's import block shows the alias developers actually write:

```go
package main

// These imports will be used later in the tutorial. If you save the file
// now, Go might complain they are unused, but that's fine.
// You may also need to run `go mod tidy` to download bubbletea and its
// dependencies.
import (
    "fmt"
    "os"

    tea "charm.land/bubbletea/v2"
)
```

The same file retracts a published version, with a comment explaining why in plain terms:

```
retract v2.0.0-beta1 // We add a "." after the "beta" in the version number.
```

Three consequences. First, v1 and v2 cannot be resolved under one import path, so an upgrade means editing imports across the tree, which is why the repository keeps UPGRADE_GUIDE_V2.md at its root and the README points migrating developers at it. Second, a retracted version is refused by the Go toolchain, so anyone who pinned v2.0.0-beta1 has to move. Third, the language floor is stated in the same file as go 1.26.0, which is a hard requirement rather than a suggestion for anyone on an older toolchain.

## View returns a tea.View value, not a rendered string

This is the API change that breaks v1 code first. The method signature in the tutorial is:

```go
func (m model) View() tea.View {
    // The header
    s := "What should we buy at the market?\n\n"

    // Iterate over our choices
    for i, choice := range m.choices {

        // Is the cursor pointing at this choice?
        cursor := " " // no cursor
        if m.cursor == i {
            cursor = ">" // cursor!
        }

        // Is this choice selected?
        checked := " " // not selected
        if _, ok := m.selected[i]; ok {
            checked = "x" // selected!
        }

        // Render the row
        s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
    }
```

The return type is tea.View, and the tutorial explains what that buys: the view declares the UI content and optionally terminal features such as alt screen mode, mouse tracking and cursor position. Redraw logic is not yours to write, because the library takes care of it. In v1 the same method returned a string, so every existing View body has to be reconsidered rather than mechanically ported. The practical effect is that a Bubble Tea program now carries display concerns in the same value as content. If you are used to a framework where the terminal mode is set once at startup, note that alt screen and mouse tracking are declared per view here, which means a single program can move between inline and full-window layouts. The repository's examples directory carries an altscreen-toggle and a fullscreen example, which is where that behaviour is demonstrated.

## Update takes a value receiver, so m.cursor-- edits a copy

The Update method in the tutorial is where the Elm Architecture becomes concrete, and it is also where a Go subtlety sits:

```go
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {

    // Is it a key press?
    case tea.KeyPressMsg:

        // Cool, what was the actual key pressed?
        switch msg.String() {

        // These keys should exit the program.
        case "ctrl+c", "q":
            return m, tea.Quit

        // The "up" and "k" keys move the cursor up
        case "up", "k":
            if m.cursor > 0 {
                m.cursor--
            }

        // The "down" and "j" keys move the cursor down
        case "down", "j":
```

Two things to notice. The receiver is a value, so m.cursor-- mutates a copy, and the update only becomes visible because the method returns the model as a tea.Model. Get that return wrong and the cursor silently stops moving while the program keeps running. The other is message dispatch: Update switches on the message type, handles tea.KeyPressMsg, and then switches again on msg.String() so keys are matched as text. ctrl+c and q return tea.Quit, which is the special command that tells the runtime to exit, and up and k plus down and j move the cursor. Messages themselves are any type, and the tutorial is explicit that they are the result of I/O such as a keypress, a timer tick or a response from a server, which is how non-key events reach the same loop.

## Terminal setup is split three ways by operating system

Look at the root of the repository and the platform story is written in filenames. Termios handling exists as termios_bsd.go, termios_unix.go, termios_other.go and termios_windows.go. Signals are split into signals_unix.go and signals_windows.go, and terminal handling into tty.go, tty_unix.go and tty_windows.go. Alongside them sit keyboard.go, key.go, mouse.go, paste.go and clipboard.go, which matches the README's claim of high-fidelity keyboard and mouse handling and native clipboard support. The dependency list explains the same shape from the other side: golang.org/x/sys for system calls, charmbracelet/x/term and charmbracelet/x/termios for terminal state, and charmbracelet/x/windows for the Windows side. The value for you is that raw mode, window size and signal restoration are handled in the library rather than in your program. The cost is that this platform surface is part of what you inherit when you vendor it, and it is the reason the module carries four separate termios files instead of one.

## ultraviolet is pinned to a commit, not to a release

Most dependencies in go.mod carry ordinary version numbers, and one does not:

```
charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7
```

That is a pseudo-version, which in Go means a specific commit rather than a tagged release. The rest of the direct requirements look conventional: charmbracelet/colorprofile at v0.4.3, charmbracelet/x/ansi at v0.11.7, charmbracelet/x/term at v0.2.2, muesli/cancelreader at v0.2.2, and github.com/lucasb-eyer/go-colorful at v1.4.0. The practical difference the pseudo-version makes is about how fixes arrive. Your build is reproducible, since that commit will always resolve to the same code, but a bug fixed in ultraviolet upstream does not reach you. It arrives when Bubble Tea updates its own go.mod, which means your terminal rendering dependencies move at the pace of the framework rather than at the pace of their projects. Width handling for CJK and emoji text is the same story, since the indirect list includes displaywidth, uax29, go-runewidth and uniseg.

## Rendering is snapshot-tested, and there is a nil renderer

Three renderer files sit at the repository root: renderer.go, cursed_renderer.go and nil_renderer.go, next to screen.go and its test, and the README attributes its speed to a cell-based renderer with built-in color downsampling. The test setup is the more interesting part. A testdata/ directory sits at the root, and two indirect dependencies are the ones you would reach for when writing golden file tests: github.com/charmbracelet/x/exp/golden and github.com/aymanbagabas/go-udiff for diffs. The consequence is that output is compared against recorded fixtures, so a change in how a frame is drawn shows up as a diff in testdata rather than as a passing test with different pixels. That is the right shape for a terminal renderer and it also means your own rendering expectations are best expressed the same way. The nil renderer is the other name worth knowing, since a renderer that draws nothing is what makes headless testing and CI runs possible.

## Bubbles holds the components, and the examples are a separate module

The README points at Bubbles for common UI components, and that division is the ecosystem answer to the comparison people search for, which is usually against a Rust TUI library. The difference is architectural rather than cosmetic. Bubble Tea is Elm-style: a model, a message loop, and a view, with no widgets in the core, so a component is your own struct and your own Update case unless you take it from Bubbles. The examples directory shows the other half of the design, and it is a separate Go module with its own go.mod and go.sum, so nothing there lands in your dependency graph. Read the example names as a capability map: canvas, cellbuffer, composable-views, cursor-style, colorprofile, autocomplete, file-picker, focus-blur, debounce, exec, http, chat, glamour for markdown, altscreen-toggle and fullscreen. Anything in that list is example code, not core API. Development tooling sits alongside: Taskfile.yaml for tasks, .golangci.yml for linting and .goreleaser.yml for release publishing.

## Conclusion

Bubble Tea fits Go developers who want an Elm-style message loop, terminal features declared in code, and a small dependency set they can read, and who are willing to write their own components or take Bubbles. It does not fit a team that wants a widget set in the box, and it is the wrong choice for a Rust project, where the comparison people search for lands on a different library entirely. Verify three things before you start: that your build toolchain is Go 1.26.0 or newer as go.mod requires, that your v1 code paths are read against UPGRADE_GUIDE_V2.md because View and the import path both changed, and that ultraviolet, which is pinned to a commit rather than a release, is acceptable in your dependency graph.

## FAQ

### how to install bubble tea go

The v2 module is charm.land/bubbletea/v2, imported in the tutorial as tea "charm.land/bubbletea/v2", and go.mod requires Go 1.26.0. The tutorial notes you may also need to run go mod tidy to download bubbletea and its dependencies.

### how to use bubble tea

A program is a model with three methods: Init returning an initial command, Update handling incoming messages, and View rendering the UI. The tutorial builds a shopping list where the model holds a choices slice, a cursor index and a selected set, and Update dispatches on tea.KeyPressMsg before switching on msg.String().

### What exactly is bubble tea?

In this repository it is a Go framework for building terminal applications based on the Elm Architecture, suitable for inline, full-window or mixed layouts. The examples directory in the repository is a separate Go module listing what it can do, from canvas and cellbuffer to file-picker and markdown rendering.

## Sources

- [Official README](https://github.com/charmbracelet/bubbletea#readme)
- [Project repository](https://github.com/charmbracelet/bubbletea)
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/charmbracelet-bubbletea
