Getting Started

Tutorial

Build your first Bubble Tea application - a simple shopping list.

Let's build a shopping list application! This tutorial assumes you have a working knowledge of Go.

The Complete Program

Here's what we're building:

Setup

Create a new Go module and install Bubble Tea:

mkdir shopping-list
cd shopping-list
go mod init shopping-list
go get github.com/charmbracelet/bubbletea/v2

Define the Model

Our model will store the application state:

package main

import (
    "fmt"
    "os"

    tea "github.com/charmbracelet/bubbletea/v2"
)

type model struct {
    choices  []string           // items on the to-do list
    cursor   int                // which item our cursor is pointing at
    selected map[int]struct{}   // which items are selected
}

Initialize the Model

Set up the initial state in the Init method:

func (m model) Init() (tea.Model, tea.Cmd) {
    m = model{
        // Our to-do list is a grocery list
        choices:  []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},

        // A map which indicates which choices are selected
        selected: make(map[int]struct{}),
    }

    return m, nil
}

Handle Updates

The Update method handles all events:

The Elm Architecture This pattern of Model → Update → View is the core of Bubble Tea.
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {

    case tea.KeyPressMsg:
        switch msg.String() {

        case "ctrl+c", "q":
            return m, tea.Quit

        case "up", "k":
            if m.cursor > 0 {
                m.cursor--
            }

        case "down", "j":
            if m.cursor < len(m.choices)-1 {
                m.cursor++
            }

        case "enter", " ":
            _, ok := m.selected[m.cursor]
            if ok {
                delete(m.selected, m.cursor)
            } else {
                m.selected[m.cursor] = struct{}{}
            }
        }
    }

    return m, nil
}

Render the View

The View method returns a string representing the UI:

func (m model) View() string {
    s := "What should we buy at the market?\n\n"

    for i, choice := range m.choices {
        cursor := " "
        if m.cursor == i {
            cursor = ">"
        }

        checked := " "
        if _, ok := m.selected[i]; ok {
            checked = "x"
        }

        s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
    }

    s += "\nPress q to quit.\n"

    return s
}

Run the Program

Finally, wire everything up in main:

func main() {
    p := tea.NewProgram(model{})
    if _, err := p.Run(); err != nil {
        fmt.Printf("Alas, there's been an error: %v", err)
        os.Exit(1)
    }
}

The Complete Code

package main

import (
    "fmt"
    "os"

    tea "github.com/charmbracelet/bubbletea/v2"
)

type model struct {
    choices  []string
    cursor   int
    selected map[int]struct{}
}

func (m model) Init() (tea.Model, tea.Cmd) {
    m = model{
        choices:  []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
        selected: make(map[int]struct{}),
    }
    return m, nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        switch msg.String() {
        case "ctrl+c", "q":
            return m, tea.Quit
        case "up", "k":
            if m.cursor > 0 {
                m.cursor--
            }
        case "down", "j":
            if m.cursor < len(m.choices)-1 {
                m.cursor++
            }
        case "enter", " ":
            _, ok := m.selected[m.cursor]
            if ok {
                delete(m.selected, m.cursor)
            } else {
                m.selected[m.cursor] = struct{}{}
            }
        }
    }
    return m, nil
}

func (m model) View() string {
    s := "What should we buy at the market?\n\n"
    for i, choice := range m.choices {
        cursor := " "
        if m.cursor == i {
            cursor = ">"
        }
        checked := " "
        if _, ok := m.selected[i]; ok {
            checked = "x"
        }
        s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
    }
    s += "\nPress q to quit.\n"
    return s
}

func main() {
    p := tea.NewProgram(model{})
    if _, err := p.Run(); err != nil {
        fmt.Printf("Alas, there's been an error: %v", err)
        os.Exit(1)
    }
}

Run It

go run main.go

Use the arrow keys (or j/k) to navigate, Enter or Space to select, and q to quit.