Core Concepts

Components

Building and composing reusable Bubble Tea components.

As your Bubble Tea application grows, you'll want to break it into reusable components. Here's how to create clean, composable UI elements.

Component Structure

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
}

Embedding Components

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(),
    )
}

Using Bubbles

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
}

Component Communication

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
}

Focus Management

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
}

icon: i-lucide-box title: Pre-built Components to: /en/bubbles

Check out the Bubbles section for ready-to-use components like text inputs, lists, spinners, and more! :::