How events flow through your Bubble Tea application
Messages (Msgs) are the events that drive your Bubble Tea application. Every interaction, timer tick, HTTP response, and system event becomes a message that flows through your Update method.
type Key struct { // Text contains the actual characters received Text string // Mod represents modifier keys (Ctrl, Alt, Shift, etc.) Mod KeyMod // Code represents the key pressed Code rune // ShiftedCode is the shifted key (e.g., 'A' when shift+a) ShiftedCode rune // BaseCode is the key on a standard PC-101 layout BaseCode rune // IsRepeat indicates if key is being held down IsRepeat bool}
Common Key Codes:
tea.KeyEnter // Enter/Return keytea.KeySpace // Space bartea.KeyBackspace // Backspacetea.KeyTab // Tabtea.KeyEsc // Escape// Arrow keystea.KeyUptea.KeyDowntea.KeyLefttea.KeyRight// Function keystea.KeyF1 through tea.KeyF63// Special keystea.KeyHometea.KeyEndtea.KeyPgUptea.KeyPgDowntea.KeyDeletetea.KeyInsert
// MouseMsg represents a mouse message. This is a generic mouse message that// can represent any kind of mouse event.type MouseMsg interface { fmt.Stringer Mouse() Mouse}
// QuitMsg signals that the program should quit.type QuitMsg struct{}// Quit is a special command that tells the Bubble Tea program to exit.func Quit() Msg { return QuitMsg{}}
Usage:
case tea.KeyPressMsg: if msg.String() == "q" { return m, tea.Quit // Returns a command that sends QuitMsg }case tea.QuitMsg: // Handle cleanup before quitting return m, nil
Sent when the program is suspended (Ctrl+Z) and resumed.From tea.go:543-558:
// SuspendMsg signals the program should suspend.type SuspendMsg struct{}func Suspend() Msg { return SuspendMsg{}}// ResumeMsg can be listened to do something once a program is resumed back// from a suspend state.type ResumeMsg struct{}
Example:
case tea.SuspendMsg: // Save state before suspending m.saveState() return m, tea.Suspendcase tea.ResumeMsg: // Refresh after resuming return m, m.fetchLatestData()
type state intconst ( stateLoading state = iota stateReady stateError)type stateChangeMsg struct { newState state}func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case stateChangeMsg: m.state = msg.newState switch msg.newState { case stateLoading: return m, loadData() case stateReady: return m, nil case stateError: return m, nil } } return m, nil}
// Goodswitch msg := msg.(type) {case tea.KeyPressMsg: // msg is now KeyPressMsg}// Bad - loses type informationswitch msg.(type) {case tea.KeyPressMsg: // msg is still tea.Msg}
Handle All Cases
Always have a default case that returns the model unchanged:
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case myMsg: // Handle default: // Don't drop messages you don't recognize } return m, nil}
Keep Messages Simple
Messages should be plain data - no methods, no behavior:
// Goodtype userLoadedMsg struct { user User}// Bad - too much behaviortype userLoadedMsg struct { user User}func (m userLoadedMsg) Process() { /* ... */ }
Document Custom Messages
Add comments explaining when and why messages are sent:
// tickMsg is sent every second to update the countdown timer.type tickMsg time.Time// dataLoadedMsg is sent when the API request completes successfully.type dataLoadedMsg struct { items []Item}