From charm-tui
Build terminal user interfaces with the Go Charm ecosystem (Bubbletea v2, Bubbles v2, Lip Gloss v2, Fang v2). Use when creating TUI applications, adding interactive terminal components, styling terminal output, building CLI tools with polished help screens, or when the user asks about Bubbletea, Bubbles, Lip Gloss, or Fang. Also trigger when reviewing or refactoring existing Charm-based code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/charm-tui:buildThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build correct, idiomatic terminal UIs with the Charm ecosystem.
Build correct, idiomatic terminal UIs with the Charm ecosystem.
WARNING: All Charm v2 libraries moved to the
charm.landvanity domain. Do NOT usegithub.com/charmbracelet/*paths — those are v0/v1.
| Library | Correct v2 Import Path |
|---|---|
| Bubbletea | charm.land/bubbletea/v2 |
| Bubbles | charm.land/bubbles/v2/<name> |
| Lip Gloss | charm.land/lipgloss/v2 |
| Fang | charm.land/fang/v2 |
Always alias bubbletea: tea "charm.land/bubbletea/v2".
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 "up", "k":
m.count++
case "down", "j":
m.count--
case "ctrl+c", "q":
return m, tea.Quit // tea.Quit — no parentheses
}
}
return m, nil
}
func (m model) View() tea.View {
s := fmt.Sprintf("Count: %d\n\nup/k: increment • down/j: decrement • q: quit\n", m.count)
return tea.NewView(s)
}
func main() {
if _, err := tea.NewProgram(model{}).Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Key v2 patterns shown:
Init() tea.Cmd — returns nil for no startup commandUpdate(tea.Msg) (tea.Model, tea.Cmd) — type-switch on messageView() tea.View — returns tea.NewView(string), not a raw stringtea.Quit — the function reference itself, no parenthesestea.KeyPressMsg — v2 key type (not tea.KeyMsg)msg.String() — easiest way to match key combinations like "ctrl+c"Every Bubbletea program is a Model implementing three methods. Init runs once at startup. Update handles every incoming message and returns a (possibly mutated) model plus an optional async Cmd. View renders the current state to a string, then wraps it in tea.NewView().
→ See references/architecture.rst for the full lifecycle, message types, and Cmd functions.
tea.Msg is any event: key press, window resize, timer tick, HTTP response. tea.Cmd is an async I/O function (func() tea.Msg) that runs off the main loop and delivers its result as a message. Never block in Update — use a Cmd instead.
→ See references/architecture.rst
Bubbles provides ready-made components (textinput, list, table, viewport, spinner, progress, filepicker, help, and more). Each is an embeddable Model with its own Update/View. You delegate to them in your parent Update.
→ See references/components.rst for all 14+ components with exact signatures.
Lip Gloss styles are immutable — every method returns a new Style. Chain calls, then call .Render(text) at the end. Use lipgloss.Color("hex") or named constants for colors.
→ See references/styling.rst
lipgloss.JoinHorizontal and lipgloss.JoinVertical place rendered string blocks side-by-side or stacked. lipgloss.Place centers content in a bounding box.
→ See references/layout.rst
Three approaches for multi-component TUIs: flat (single model, state enum), model-stack (child models as struct fields), hybrid (stack + mode enum). Focus management uses Tab/Shift-Tab cycling. → See references/patterns.rst
teatest provides NewTestModel, WaitFor, and RequireEqualOutput for golden-file testing. VHS records .tape scripts for visual regression.
→ See references/testing.rst
Fang wraps a Cobra command with styled help, errors, man pages, and shell completions via fang.Execute(ctx, cobraCmd, opts...).
→ See references/fang.rst
Init, Update, or View will not compile.tea.WindowSizeMsg. Store width and height in your model; recalculate layouts in View. Hardcoded dimensions break on resize.case "ctrl+c" (at minimum) → return m, tea.Quit.tea.Quit without parentheses. It is a tea.Cmd value (a function reference). tea.Quit() calls the function and returns a Msg, which is wrong.Update, call child, cmd = child.Update(msg) and reassign the child field. Forgetting the reassignment is a silent bug.tea.Batch for multiple Cmds. return m, tea.Batch(cmd1, cmd2) — never start goroutines manually.tea.Sequence for ordered Cmds. When Cmd B depends on Cmd A completing first.v.AltScreen = true in View(). In v2, alt screen is declared on the View struct, not via tea.WithAltScreen() in NewProgram.tea.NewView(s) in View(). The View() method returns tea.View, not string. tea.NewView(s) is the constructor.msg.String(). Returns human-readable strings like "ctrl+c", "shift+enter", "space" (not " ").| Anti-Pattern | Correct Pattern |
|---|---|
github.com/charmbracelet/bubbletea | charm.land/bubbletea/v2 |
return m, tea.Quit() (parens) | return m, tea.Quit (no parens) |
View() string | View() tea.View |
return tea.NewView(s) (string) → return s | Always return tea.NewView(s) |
case " ": for space | case "space": |
case tea.KeyMsg: (v1 type) | case tea.KeyPressMsg: (v2 type) |
tea.WithAltScreen() in NewProgram | v.AltScreen = true in View() |
| Blocking I/O in Update | Return a tea.Cmd for async I/O |
| Raw goroutines | tea.Cmd functions |
Calling .Update() without reassigning | m.child, cmd = m.child.Update(msg) |
| Inventing method names | Check references/components.rst |
| File | Contents |
|---|---|
| references/architecture.rst | Model interface, Program lifecycle, all Msg/Cmd types |
| references/components.rst | All 14+ Bubbles components — constructors, fields, methods |
| references/styling.rst | Lip Gloss Style API, colors, borders, text attributes |
| references/layout.rst | JoinHorizontal/Vertical, Place, responsive patterns |
| references/patterns.rst | Composition strategies, focus management, error handling |
| references/testing.rst | teatest, golden files, VHS tape scripts |
| references/fang.rst | Fang v2 + Cobra CLI polish |
| references/recipes.rst | 5 complete runnable examples with output mockups |
Guides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Synthesizes the current conversation into a structured spec (PRD) and publishes it to the project issue tracker with a ready-for-agent label, without interviewing the user.
npx claudepluginhub nq-rdl/agent-extensions --plugin charm-tui