Bubbles: ready-made TUI widgets for Bubble Tea apps
Charm's component library gives terminal UIs spinners, inputs, tables and more.
Charm's Bubbles is what you reach for when your Bubble Tea application needs more than raw key handling — it wants fields to type into, tables to scroll, spinners that spin while work happens. The library packages those pieces as individual Go components, each a self-contained Model that plugs straight into the Bubble Tea update loop.
The module is charm.land/bubbles/v2, MIT-licensed, and sits at release v2.2.1. It depends on Bubble Tea v2 (charm.land/bubbletea/v2) for the runtime and Lip Gloss v2 (charm.land/lipgloss/v2) for styling. The top-level bubbles.go is just a package doc: "some components for Bubble Tea applications," used in production by Charm's own tools.
What ships inside
The repo breaks into one directory per component (including tree/), each an independent importable package:
textinput/— single-line input with unicode support, paste handling, echo modes (normal, password-masked, hidden), and in-place scrolling when the value outgrows its widthtextarea/— multi-line counterpart, with vertical scrolling and a memoization cache underinternal/memoization/to keep large buffers responsivetable/— tabular data display with column navigation and vertical scrolllist/— a "batteries-included" item browser with pagination, fuzzy filtering (viasahilm/fuzzy) and auto-generated helpviewport/— vertically scrolling content area, optional pager keybindings and mouse wheel support, plus a high-performance mode for alternate-screen appsspinner/,progress/,paginator/,timer/,stopwatch/— the status-and-progress familyfilepicker/— directory navigation and file selection with optional extension filteringhelp/— auto-generated keybinding help that truncates gracefullykey/— non-visual binding management so users can remap keys; also feeds the help viewscursor/— a virtual cursor shared by textinput and textarea, with its own blink timing
Two internal packages support these: internal/runeutil/ for rune-level string handling and internal/memoization/, an LRU cache "designed to improve performance in textarea."
How a component fits together
Every bubble follows the same Bubble Tea contract. You create it with a constructor, feed it messages through Update, and render it with View. The spinner shows the pattern clearly:
m := spinner.New()
New takes functional options, defaults to the Line spinner, and ships with built-in frame sets you can select among — Dot, MiniDot, Pulse, Globe, Moon, Monkey — or you can pass your own frames. Each has a frame rate (FPS) and the model tracks an internal ID so animation messages are only accepted by the spinner that sent them — the same trick the cursor uses for blink timing.
The key package is where you define bindings, separating what keys do from how they're described in help text:
type KeyMap struct {
Up key.Binding
Down key.Binding
}
var DefaultKeyMap = KeyMap{
Up: key.NewBinding(
key.WithKeys("k", "up"), // actual keybindings
key.WithHelp("↑/k", "move up"), // help text shown to users
),
Down: key.NewBinding(
key.WithKeys("j", "down"),
key.WithHelp("↓/j", "move down"),
),
}
In your Update, you match incoming tea.KeyPressMsg against the bindings with key.Matches.
The v2 migration
Bubbles v1 lived at github.com/charmbracelet/bubbles. Version 2 moved to a new module path and, per the bundled UPGRADE_GUIDE_V2.md, requires upgrading Bubble Tea and Lip Gloss together:
go get charm.land/bubbletea/v2@latest
go get charm.land/bubbles/v2@latest
go get charm.land/lipgloss/v2@latest
The guide is written explicitly for both humans and LLM-assisted migration tools, walking through every breaking change per component — import path swaps, constructor renames like spinner.NewModel() to spinner.New(), options moved into functional arguments such as stopwatch.New(stopwatch.WithInterval(...)). The styling system was restructured across components, and new key bindings were added (textarea gained PageUp/PageDown). A "Removed Symbols Reference" lists what no longer exists.
Getting started
Because each component is its own package under the module path, you import only what you need:
import (
tea "charm.land/bubbletea/v2"
"charm.land/bubbles/v2/textinput"
)
The README points to example programs living in the Bubble Tea repository rather than inline — basic and fancy lists, a chat-style textarea, animated and static progress bars, a pager viewport, timer and stopwatch demos. Community-maintained bubbles are collected under Charm & Friends' "additional-bubbles" repo.
The project uses a Taskfile.yaml for its task runner rather than plain Make targets. It's part of the wider Charm ecosystem — Bubble Tea for the runtime, Lip Gloss for styling, and Bubbles as the component layer on top.
What it is not
Bubbles does not render your whole application; it supplies pieces you compose inside a single Model. The viewport pairs with Reflow for ANSI-aware wrapping. The paginator can be used purely for logic if you'd rather draw your own dots and numbers. And the help component generates itself from whatever keybindings you register, so there's no separate documentation to keep in sync.
For anyone building a terminal UI in Go — a TUI client, an interactive config editor, a dashboard that runs in SSH sessions — Bubbles is the layer that turns raw Bubble Tea plumbing into something resembling a real interface. The v2 release tightens it around the modern Charm stack and gives you a migration path instead of leaving v1 users stranded.
charmbracelet/bubbles: 478 KB in 106 files, indexed Nov 10, 2026 after 0.35 hours of indexing. License: MIT. Source: https://github.com/charmbracelet/bubbles.
Ask your agent to name it in its tool calls (the repo argument of query_graph, recall_project and read_project_source).
Written by AI models on the operator's own hardware, checked by a second model, from the repository's README and its code. Back to the shelf