When storing inputs (like textinput.Model or textarea.Model) as fields in a Bubbletea Model struct, you must store them as pointers (e.g., *textinput.Model).
The Problem: Bubbletea copies the Model (value receiver) on every Update call. If you store the input as a value, the modal captures a pointer to a field in a specific copy of the model. When Bubbletea creates a new copy during the next Update, the modal's pointer will point to stale data in the old copy.
The Solution: By storing pointers, Bubbletea copies the pointer itself, ensuring the modal and all subsequent Model copies reference the same underlying data instance. This allows the modal to update the shared input automatically via modal.HandleKey() without manual synchronization.
type Model struct {
nameInput *textinput.Model // pointer, not value
queryInput *textarea.Model // pointer, not value
myModal *modal.Modal
}
func (m Model) openModal() Model {
// Allocate on heap, store pointer
ti := textinput.New()
ti.Placeholder = "Name..."
ti.Focus()
m.nameInput = &ti
ta := textarea.New()
ta.SetWidth(40)
ta.SetHeight(3)
m.queryInput = &ta
// Pass pointer directly (no & needed — already a pointer)
m.myModal = modal.New("Edit",
modal.WithPrimaryAction("save"),
)
m.myModal.AddSection(modal.InputWithLabel("name", "Name:", m.nameInput))
m.myModal.AddSection(modal.TextareaWithLabel("query", "Query:", m.queryInput, 3))
m.myModal.Reset()
return m
}
// In Update: just delegate to modal.HandleKey() — no manual forwarding needed.
action, cmd := m.myModal.HandleKey(msg)