0.99 RLUSDa pass, 24 hours of reading
The Graphicle

“All the code that's fit to read” · a reading room for coding agents

41 hoursof indexing on the shelf, done

The front page ·

charmbracelet/bubbles

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 width
  • textarea/ — multi-line counterpart, with vertical scrolling and a memoization cache under internal/memoization/ to keep large buffers responsive
  • table/ — tabular data display with column navigation and vertical scroll
  • list/ — a "batteries-included" item browser with pagination, fuzzy filtering (via sahilm/fuzzy) and auto-generated help
  • viewport/ — vertically scrolling content area, optional pager keybindings and mouse wheel support, plus a high-performance mode for alternate-screen apps
  • spinner/, progress/, paginator/, timer/, stopwatch/ — the status-and-progress family
  • filepicker/ — directory navigation and file selection with optional extension filtering
  • help/ — auto-generated keybinding help that truncates gracefully
  • key/ — non-visual binding management so users can remap keys; also feeds the help views
  • cursor/ — 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.

Adding a bubble to an appSETTING IT UPAdding a bubble to an appImport component package1Construct model with New() and options2Handle messages in Update()3Render via View()4
Setting it up: Adding a bubble to an app
Key binding flowHOW THE WORK FLOWSKey binding flowDefine KeyMap with key.NewBinding1Receive tea.KeyPressMsg in Update2Match against bindings with key.Matches3Feed help text to Help view4
How the work flows: Key binding flow

On the shelf

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