As your Bubble Tea application grows, you'll want to break it into reusable components. Here's how to create clean, composable UI elements.
A component is typically a struct with its own Init, Update, and View methods:
type TextInput struct {
value string
cursor int
focused bool
placeholder string
}
func NewTextInput() TextInput {
return TextInput{
placeholder: "Type here...",
}
}
func (t TextInput) Init() tea.Cmd {
return nil
}
func (t TextInput) Update(msg tea.Msg) (TextInput, tea.Cmd) {
// Handle input logic
return t, nil
}
func (t TextInput) View() string {
if t.value == "" {
return t.placeholder
}
return t.value
}
Compose your main model with components:
type model struct {
nameInput TextInput
emailInput TextInput
focused int
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch m.focused {
case 0:
m.nameInput, cmd = m.nameInput.Update(msg)
case 1:
m.emailInput, cmd = m.emailInput.Update(msg)
}
return m, cmd
}
func (m model) View() string {
return fmt.Sprintf(
"Name: %s\nEmail: %s",
m.nameInput.View(),
m.emailInput.View(),
)
}
The Bubbles library provides pre-built components:
import (
"github.com/charmbracelet/bubbles/v2/textinput"
"github.com/charmbracelet/bubbles/v2/spinner"
"github.com/charmbracelet/bubbles/v2/list"
)
type model struct {
input textinput.Model
spinner spinner.Model
list list.Model
}
Components communicate through messages:
// Define a message for when input is submitted
type inputSubmittedMsg struct {
value string
}
func (t TextInput) Update(msg tea.Msg) (TextInput, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
if msg.String() == "enter" {
return t, func() tea.Msg {
return inputSubmittedMsg{t.value}
}
}
}
return t, nil
}
Manage focus between components:
type model struct {
inputs []textinput.Model
focused int
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "tab":
m.inputs[m.focused].Blur()
m.focused = (m.focused + 1) % len(m.inputs)
m.inputs[m.focused].Focus()
}
}
// Update focused input
var cmd tea.Cmd
m.inputs[m.focused], cmd = m.inputs[m.focused].Update(msg)
return m, cmd
}
Check out the Bubbles section for ready-to-use components like text inputs, lists, spinners, and more! :::