Search tldr

Tips for building Bubble Tea programs

Practical architecture, debugging, testing, and layout guidance for building responsive Go terminal UIs with Bubble Tea. The central discipline is to keep the event loop fast, model state message-driven, and concurrency-aware.

Share

Tips for building Bubble Tea programs

Author: Louis Garman | Published: 2024-08-24 | Generated: 2026-04-15 | Domain: leg100.github.io
Tags: ‘#golang’ ‘#bubbletea’ ‘#tui’ ‘#terminal’ ‘#testing’ ‘#concurrency’


TLDR

Bubble Tea applications stay responsive only when Update() and View() remain fast; slow work belongs in tea.Cmd functions, whose results return as messages. Structure larger TUIs as a tree of models, route messages deliberately, and do not assume concurrently produced command messages arrive in order. The article also recommends practical workflows for message logging, TTY-friendly live reloads, resilient layouts, end-to-end testing with teatest, and recording demos with VHS.

Key Takeaways

  • Keep the event loop non-blocking: Bubble Tea processes each message by calling Update(), running its returned command separately, then rendering View(); expensive work in either method causes queued messages, lag, and apparent UI stalls. Return expensive operations as tea.Cmd functions instead.
  • Preserve message-driven state changes: Pointer receivers can be useful, but mutating a model from goroutines outside the event loop creates races and nondeterministic rendering. Make state changes in Update() and return the updated model through Bubble Tea’s normal flow.
  • Concurrent commands have no ordering guarantee: Input events originate from a single routine and retain order, but tea.Cmd results and explicit concurrent Send(msg) calls can arrive in arbitrary order. Use direct updates when safe, tea.Sequence when ordering is required, or redesign to avoid ordering dependencies.
  • Scale with a model tree: Treat the root model as a message router and screen compositor, with child models handling focused interactions and rendering their own views. Route global keys at the root, user actions to the active child, and shared events such as tea.WindowSizeMsg to all relevant children.
  • Invest in developer tooling: Dump messages to a file under DEBUG, use TTY-compatible rebuild workflows, calculate layout space from rendered lipgloss.Height()/Width() values rather than hard-coded dimensions, test full interactions with teatest, and version VHS tapes alongside code and docs.

Images & Media

  • PUG tasks screenshot — Screenshot of PUG, the author’s full-screen Terraform terminal interface.
  • Tree of models in PUG — Diagram showing message routing through PUG’s hierarchy of Bubble Tea models.
  • Working layout — Header, content, and footer layout before a border changes widget height.
  • Broken layout — Layout failure caused by stale hard-coded height arithmetic after adding a header border.
  • Fixed layout — Corrected layout using calculated rendered heights.
  • Unreset terminal following panic — Demonstration of a terminal left in an unusable state after an unrecovered command panic.
  • PUG demo — Animated VHS-recorded demonstration of PUG.
  • Bubble Tea — Go framework for building terminal user interfaces.
  • PUG — The author’s Bubble Tea-based terminal UI for driving Terraform.
  • go-spew — Pretty-printer recommended for dumping incoming Bubble Tea messages during debugging.
  • Bubble Tea Sequence documentation — API for executing Bubble Tea commands sequentially.
  • Pointer receiver discussion — Community discussion on value versus pointer receivers in Bubble Tea models.
  • Bubble Tea terminal-reset issue — Open issue covering terminal recovery when a command panics.
  • teatest — Experimental Charm library for end-to-end Bubble Tea testing.
  • Charm’s teatest announcement — Guide to interaction tests and golden-file snapshots with teatest.
  • VHS — Declarative tool for recording terminal GIFs and screenshots.

Keep reading