Documentation Index
Fetch the complete documentation index at: https://mintlify.com/charmbracelet/bubbletea/llms.txt
Use this file to discover all available pages before exploring further.
The Simple List example demonstrates how to create a compact, customized list interface using Bubble Tea’s list component. This example shows you how to create custom item delegates and styling for a minimal appearance.
Overview
This example creates a dinner menu selector with:
- Custom item rendering
- Minimal, compact styling
- Custom delegate for list items
- Simple selection handling
Complete Example
Here’s the full implementation from examples/list-simple/main.go:
package main
import (
"fmt"
"io"
"os"
"strings"
"charm.land/bubbles/v2/list"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
)
const listHeight = 14
type styles struct {
title lipgloss.Style
item lipgloss.Style
selectedItem lipgloss.Style
pagination lipgloss.Style
help lipgloss.Style
quitText lipgloss.Style
}
func newStyles(darkBG bool) styles {
var s styles
s.title = lipgloss.NewStyle().MarginLeft(2)
s.item = lipgloss.NewStyle().PaddingLeft(4)
s.selectedItem = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
s.pagination = list.DefaultStyles(darkBG).PaginationStyle.PaddingLeft(4)
s.help = list.DefaultStyles(darkBG).HelpStyle.PaddingLeft(4).PaddingBottom(1)
s.quitText = lipgloss.NewStyle().Margin(1, 0, 2, 4)
return s
}
type item string
func (i item) FilterValue() string { return "" }
type itemDelegate struct {
styles *styles
}
func (d itemDelegate) Height() int { return 1 }
func (d itemDelegate) Spacing() int { return 0 }
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
i, ok := listItem.(item)
if !ok {
return
}
str := fmt.Sprintf("%d. %s", index+1, i)
fn := d.styles.item.Render
if index == m.Index() {
fn = func(s ...string) string {
return d.styles.selectedItem.Render("> " + strings.Join(s, " "))
}
}
fmt.Fprint(w, fn(str))
}
type model struct {
list list.Model
choice string
styles styles
quitting bool
}
func initialModel() model {
items := []list.Item{
item("Ramen"),
item("Tomato Soup"),
item("Hamburgers"),
item("Cheeseburgers"),
item("Currywurst"),
item("Okonomiyaki"),
item("Pasta"),
item("Fillet Mignon"),
item("Caviar"),
item("Just Wine"),
}
const defaultWidth = 20
l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
l.Title = "What do you want for dinner?"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
m := model{list: l}
m.updateStyles(true) // default to dark styles.
return m
}
func (m *model) updateStyles(isDark bool) {
m.styles = newStyles(isDark)
m.list.Styles.Title = m.styles.title
m.list.Styles.PaginationStyle = m.styles.pagination
m.list.Styles.HelpStyle = m.styles.help
m.list.SetDelegate(itemDelegate{styles: &m.styles})
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.list.SetWidth(msg.Width)
return m, nil
case tea.KeyPressMsg:
switch keypress := msg.String(); keypress {
case "q", "ctrl+c":
m.quitting = true
return m, tea.Quit
case "enter":
i, ok := m.list.SelectedItem().(item)
if ok {
m.choice = string(i)
}
return m, tea.Quit
}
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m model) View() tea.View {
if m.choice != "" {
return tea.NewView(m.styles.quitText.Render(fmt.Sprintf("%s? Sounds good to me.", m.choice)))
}
if m.quitting {
return tea.NewView(m.styles.quitText.Render("Not hungry? That's cool."))
}
return tea.NewView("\n" + m.list.View())
}
func main() {
if _, err := tea.NewProgram(initialModel()).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
Key Concepts
Custom Item Type
The example uses a simple string-based item type:
type item string
func (i item) FilterValue() string { return "" }
The FilterValue() method is required by the list interface. Returning an empty string disables filtering.
Custom Delegate
The itemDelegate struct controls how each item is rendered:
type itemDelegate struct {
styles *styles
}
func (d itemDelegate) Height() int { return 1 }
func (d itemDelegate) Spacing() int { return 0 }
By setting Height() to 1 and Spacing() to 0, we create a compact list appearance.
Custom Rendering
The Render method provides full control over item appearance:
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
i, ok := listItem.(item)
if !ok {
return
}
str := fmt.Sprintf("%d. %s", index+1, i)
fn := d.styles.item.Render
if index == m.Index() {
fn = func(s ...string) string {
return d.styles.selectedItem.Render("> " + strings.Join(s, " "))
}
}
fmt.Fprint(w, fn(str))
}
This renders items with:
- Numbered format (“1. Ramen”)
- Different styling for selected items (adds ”>” prefix)
- Custom colors via Lip Gloss styles
Style Management
The example demonstrates adaptive styling based on terminal background:
func newStyles(darkBG bool) styles {
var s styles
s.title = lipgloss.NewStyle().MarginLeft(2)
s.item = lipgloss.NewStyle().PaddingLeft(4)
s.selectedItem = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
s.pagination = list.DefaultStyles(darkBG).PaginationStyle.PaddingLeft(4)
s.help = list.DefaultStyles(darkBG).HelpStyle.PaddingLeft(4).PaddingBottom(1)
s.quitText = lipgloss.NewStyle().Margin(1, 0, 2, 4)
return s
}
Running the Example
cd examples/list-simple
go run .
Use arrow keys to navigate, Enter to select, and q or Ctrl+C to quit.
Comparison with Default List
For a more feature-rich list with built-in filtering and status bar, see the Default List example.
Source Code
View the complete source code: examples/list-simple