name: charm-bracelet-tui description: > Complete guide for building beautiful, interactive Go TUI (terminal UI) applications using the Charm Bracelet ecosystem. ALWAYS use this skill when the user mentions: bubbletea, bubbles, lipgloss, glamour, huh, harmonica, bubblezone, ntcharts, TUI in Go, terminal user interface in Go, terminal app with Go, CLI with interactive UI, charm.sh, charm.land, or wants to build any kind of interactive terminal application. Also trigger when building Go CLI tools that need forms, menus, progress bars, spinners, charts, markdown rendering, styled output, or mouse support. This covers the full stack from framework to styling to widgets to animations.
Charm Bracelet TUI Stack
The Charm Bracelet ecosystem is a collection of Go libraries for building beautiful terminal UIs. They are designed to work together but can also be used independently.
Current versions: All core Charm libs are on v2 as of early 2026 (import paths use charm.land/<lib>/v2).
The Stack at a Glance
┌─────────────────────────────────────────────────────────────┐
│ Your TUI Application │
├──────────────────────────┬──────────────────────────────────┤
│ huh (forms/prompts) │ Bubbles (pre-built components) │
├──────────────────────────┴──────────────────────────────────┤
│ BubbleTea (MVU framework) │
├──────────────────┬──────────────┬───────────────────────────┤
│ Lip Gloss │ Glamour │ Harmonica │
│ (styling) │ (markdown) │ (animation) │
├──────────────────┴──────────────┴───────────────────────────┤
│ BubbleZone (mouse tracking) │ ntcharts (charts) │
└─────────────────────────────────────────────────────────────┘
Library Roles
| Library | Role | Import |
|---|---|---|
| BubbleTea | MVU framework — the backbone of every app | charm.land/bubbletea/v2 |
| Bubbles | Pre-built TUI components (inputs, lists, etc.) | charm.land/bubbles/v2 (note: check go.dev for exact path) |
| Lip Gloss | Styling, layout, colors, borders, tables, trees | charm.land/lipgloss/v2 |
| Glamour | Stylesheet-based Markdown renderer | github.com/charmbracelet/glamour |
| Huh | Terminal forms and interactive prompts | charm.land/huh/v2 |
| Harmonica | Physics-based spring animations | github.com/charmbracelet/harmonica |
| BubbleZone | Mouse event tracking across components | github.com/lrstanley/bubblezone/v2 |
| ntcharts | Terminal charts (bar, line, sparkline, heatmap, etc.) | github.com/NimbleMarkets/ntcharts |
Quick Reference: Which Library for What?
- Need a TUI framework? → BubbleTea (always the foundation)
- Need forms / user input collection? → Huh (or Bubbles textinput/textarea)
- Need styled output / layouts? → Lip Gloss
- Need to render Markdown? → Glamour
- Need pre-built components? → Bubbles (spinner, progress, list, table, viewport, filepicker...)
- Need smooth animations? → Harmonica
- Need mouse click detection per-component? → BubbleZone
- Need charts / graphs? → ntcharts
How They Work Together
BubbleTea + Lip Gloss (the core combo)
BubbleTea handles the update loop; Lip Gloss handles the rendering. Your View() method returns a tea.View built by rendering Lip Gloss styles:
func (m model) View() tea.View {
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FF75B7")).Render("My App")
body := lipgloss.NewStyle().Padding(1, 2).Render("Hello world")
return tea.NewView(lipgloss.JoinVertical(lipgloss.Left, title, body))
}
BubbleTea + Huh (embedded forms)
huh.Form is a tea.Model, so embed it directly:
type Model struct {
form *huh.Form
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
form, cmd := m.form.Update(msg)
if f, ok := form.(*huh.Form); ok { m.form = f }
return m, cmd
}
func (m Model) View() string { return m.form.View() }
BubbleTea + BubbleZone (mouse support)
Wrap your root View() in zone.Scan(), mark individual areas with zone.Mark(id, content), check zone.Get(id).InBounds(mouseMsg) in Update().
BubbleTea + ntcharts (charts)
ntcharts components are standalone (they have their own Draw()/View() methods), so embed them in your model and call them during Update() and View().
Getting Started
# Core framework
go get charm.land/bubbletea/v2
# Styling
go get charm.land/lipgloss/v2
# Pre-built components
go get github.com/charmbracelet/bubbles
# Forms
go get charm.land/huh/v2
# Markdown
go get github.com/charmbracelet/glamour
# Animations
go get github.com/charmbracelet/harmonica
# Mouse tracking
go get github.com/lrstanley/bubblezone/v2
# Charts
go get github.com/NimbleMarkets/ntcharts
Minimal BubbleTea App (v2 API)
package main
import (
"fmt"
"os"
tea "charm.land/bubbletea/v2"
)
type model struct{ count int }
func (m model) Init() tea.Cmd { return 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": m.count--
}
}
return m, nil
}
func (m model) View() tea.View {
return tea.NewView(fmt.Sprintf("Count: %d\n\nPress q to quit.", m.count))
}
func main() {
p := tea.NewProgram(model{})
if _, err := p.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Reference Files
Read the relevant reference file(s) for deep implementation details:
references/bubbletea.md— BubbleTea framework: MVU lifecycle, commands, messages, v2 View APIreferences/lipgloss.md— Lip Gloss: styles, colors, layouts, tables, trees, lists, compositingreferences/bubbles.md— Bubbles components: spinner, textinput, textarea, list, table, viewport, progress, filepicker, timer, helpreferences/glamour.md— Glamour: markdown rendering, themes, custom stylesreferences/huh.md— Huh: forms, fields, dynamic forms, BubbleTea integration, spinnerreferences/harmonica.md— Harmonica: spring animations, damping ratios, BubbleTea integrationreferences/bubblezone.md— BubbleZone: mouse zones, InBounds, global vs local managerreferences/ntcharts.md— ntcharts: canvas, bar, line, sparkline, heatmap, OHLC, time series, waveline
When to Read Which
| Task | Reference(s) |
|---|---|
| Building a new TUI app from scratch | bubbletea.md → lipgloss.md |
| Adding forms/prompts | huh.md |
| Adding pre-built widgets | bubbles.md |
| Rendering markdown | glamour.md |
| Adding animations | harmonica.md |
| Mouse click detection | bubblezone.md |
| Charts and data visualization | ntcharts.md |
| Complex layouts / styling | lipgloss.md |
Common Gotchas
- v2 import paths: Core Charm libs moved to
charm.land/<lib>/v2in 2025-2026.github.com/charmbracelet/*paths still work for some (glamour, harmonica), but prefercharm.landfor bubbletea, lipgloss, huh. - BubbleTea v2 View:
View()now returnstea.View(notstring). Usetea.NewView(str)to wrap string content. - AltScreen for BubbleZone: BubbleZone requires alt-screen mode — set
view.AltScreen = truein your root view. - Dynamic hex colors in Lip Gloss: Use
lipgloss.Color("#RRGGBB")— Lip Gloss will automatically downsample for lower-color terminals. - Lip Gloss Width vs len(): Always use
lipgloss.Width(str)instead oflen()— ANSI codes inflatelen(). - BubbleZone zone.Scan() at root only: Only wrap your root model's view output with
zone.Scan(). - ntcharts: Call
.Draw()before.View()to rasterize the chart each frame.