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.
Views are how you render your application’s user interface. They declare what should be displayed, and Bubble Tea handles how to render it efficiently.
The View Type
From tea.go:81-189 :
// View represents a terminal view that can be composed of multiple layers.
// It can also contain a cursor that will be rendered on top of the layers.
type View struct {
// Content is the screen content of the view. It holds styled strings that
// will be rendered to the terminal when the view is rendered.
Content string
// OnMouse is an optional mouse message handler
OnMouse func ( msg MouseMsg ) Cmd
// Cursor represents the cursor position, style, and visibility
Cursor * Cursor
// BackgroundColor sets the terminal background color
BackgroundColor color . Color
// ForegroundColor sets the terminal foreground color
ForegroundColor color . Color
// WindowTitle sets the terminal window title
WindowTitle string
// ProgressBar shows a progress bar in the terminal
ProgressBar * ProgressBar
// AltScreen puts the program in alternate screen buffer (full window mode)
AltScreen bool
// ReportFocus enables reporting when the terminal gains and loses focus
ReportFocus bool
// DisableBracketedPasteMode disables bracketed paste mode
DisableBracketedPasteMode bool
// MouseMode sets the mouse mode
MouseMode MouseMode
// KeyboardEnhancements describes keyboard enhancement features to request
KeyboardEnhancements KeyboardEnhancements
}
Creating Views
NewView
The simplest way to create a view:
From tea.go:67-79 :
// NewView is a helper function to create a new [View] with the given styled
// string.
func NewView ( s string ) View
Example:
func ( m model ) View () tea . View {
return tea . NewView ( "Hello, World!" )
}
Manual Construction
For more control, create the View directly:
func ( m model ) View () tea . View {
var v tea . View
v . Content = m . render ()
v . WindowTitle = "My App"
v . MouseMode = tea . MouseModeAllMotion
return v
}
SetContent Method
From tea.go:249-261 :
// SetContent is a helper method to set the content of a [View]
func ( v * View ) SetContent ( s string )
Example:
func ( m model ) View () tea . View {
var v tea . View
v . SetContent ( "Hello!" )
return v
}
Content Rendering
Basic Content
Content is a string with ANSI escape codes for styling:
func ( m model ) View () tea . View {
s := "Counter: " + strconv . Itoa ( m . count ) + " \n "
s += "Press 'q' to quit \n "
return tea . NewView ( s )
}
Using Lip Gloss for Styling
Lip Gloss is the recommended way to style terminal output:
import " github.com/charmbracelet/lipgloss "
var (
titleStyle = lipgloss . NewStyle ().
Bold ( true ).
Foreground ( lipgloss . Color ( "#FAFAFA" )).
Background ( lipgloss . Color ( "#7D56F4" )).
Padding ( 0 , 1 )
textStyle = lipgloss . NewStyle ().
Foreground ( lipgloss . Color ( "#999999" ))
)
func ( m model ) View () tea . View {
title := titleStyle . Render ( "My Application" )
text := textStyle . Render ( "Welcome to Bubble Tea!" )
content := lipgloss . JoinVertical ( lipgloss . Left ,
title ,
"" ,
text ,
)
return tea . NewView ( content )
}
Multi-line Content
Use string concatenation or strings.Builder:
String Concatenation
strings.Builder
fmt.Sprintf
func ( m model ) View () tea . View {
s := ""
s += "Line 1 \n "
s += "Line 2 \n "
s += "Line 3 \n "
return tea . NewView ( s )
}
func ( m model ) View () tea . View {
var b strings . Builder
b . WriteString ( "Line 1 \n " )
b . WriteString ( "Line 2 \n " )
b . WriteString ( "Line 3 \n " )
return tea . NewView ( b . String ())
}
func ( m model ) View () tea . View {
s := fmt . Sprintf ( `
Line 1
Line 2
Line 3
` )
return tea . NewView ( s )
}
View Features
Window Title
Set the terminal window title:
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
v . WindowTitle = fmt . Sprintf ( "MyApp - %s " , m . currentFile )
return v
}
See tutorials/basics/main.go:79 for an example.
Alternate Screen
Switch to full-screen mode:
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
v . AltScreen = true // Full window mode
return v
}
Alternate Screen (AltScreen) is like what you see in vim or less - when you exit, the terminal returns to its previous state.
Cursor
From tea.go:336-361 :
// Cursor represents a cursor on the terminal screen.
type Cursor struct {
Position // X, Y coordinates
Color color . Color
Shape CursorShape
Blink bool
}
func NewCursor ( x , y int ) * Cursor
Example:
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
// Show cursor at input position
v . Cursor = tea . NewCursor ( m . cursorX , m . cursorY )
v . Cursor . Shape = tea . CursorBar
v . Cursor . Blink = true
return v
}
Cursor Shapes:
CursorBlock - Block cursor (█)
CursorUnderline - Underline cursor (_)
CursorBar - Bar cursor (|)
Mouse Support
From tea.go:263-286 :
type MouseMode int
const (
MouseModeNone // No mouse events
MouseModeCellMotion // Click, release, wheel, and drag events
MouseModeAllMotion // All mouse events including movement
)
Enable mouse support:
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
v . MouseMode = tea . MouseModeAllMotion
return v
}
Handle mouse events:
func ( m model ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg .( type ) {
case tea . MouseClickMsg :
mouse := msg . Mouse ()
m . handleClick ( mouse . X , mouse . Y )
}
return m , nil
}
OnMouse Handler
Handle mouse events based on view content:
From tea.go:97-125 :
func ( m model ) View () tea . View {
content := "Click [here] to continue"
v := tea . NewView ( content )
v . OnMouse = func ( msg tea . MouseMsg ) tea . Cmd {
mouse := msg . Mouse ()
// Check if click is on "here"
start := strings . Index ( content , "[here]" )
end := start + 6
if mouse . Y == 0 && mouse . X >= start && mouse . X < end {
return func () tea . Msg {
return clickedHereMsg {}
}
}
return nil
}
return v
}
Progress Bar
From tea.go:311-334 :
type ProgressBar struct {
State ProgressBarState
Value int // 0-100
}
const (
ProgressBarNone
ProgressBarDefault
ProgressBarError
ProgressBarIndeterminate
ProgressBarWarning
)
func NewProgressBar ( state ProgressBarState , value int ) * ProgressBar
Example:
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
// Show download progress
v . ProgressBar = tea . NewProgressBar (
tea . ProgressBarDefault ,
m . downloadPercent ,
)
return v
}
Progress bar support depends on the terminal. Windows Terminal and some other modern terminals support this feature.
Terminal Colors
Set default terminal colors:
import " image/color "
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
v . BackgroundColor = color . RGBA { 0x 1a , 0x 1b , 0x 26 , 0x ff }
v . ForegroundColor = color . RGBA { 0x c0 , 0x ca , 0x f5 , 0x ff }
return v
}
Keyboard Enhancements
Request advanced keyboard features:
From tea.go:195-247 :
type KeyboardEnhancements struct {
// ReportEventTypes requests key repeat and release events
ReportEventTypes bool
}
Example:
func ( m model ) View () tea . View {
v := tea . NewView ( m . render ())
// Request key release events
v . KeyboardEnhancements . ReportEventTypes = true
return v
}
func ( m model ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg .( type ) {
case tea . KeyboardEnhancementsMsg :
if msg . ReportEventTypes {
// We can now receive KeyReleaseMsg!
}
case tea . KeyReleaseMsg :
// Handle key release
}
return m , nil
}
View Patterns
Responsive Layout
Adjust to terminal size:
func ( m model ) View () tea . View {
if m . width < 80 {
// Narrow layout
return tea . NewView ( m . renderNarrow ())
}
// Wide layout
return tea . NewView ( m . renderWide ())
}
func ( m model ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg .( type ) {
case tea . WindowSizeMsg :
m . width = msg . Width
m . height = msg . Height
}
return m , nil
}
Component Composition
type model struct {
header headerComponent
body bodyComponent
footer footerComponent
}
func ( m model ) View () tea . View {
var b strings . Builder
// Compose views from components
b . WriteString ( m . header . View ())
b . WriteString ( " \n " )
b . WriteString ( m . body . View ())
b . WriteString ( " \n " )
b . WriteString ( m . footer . View ())
return tea . NewView ( b . String ())
}
Conditional Rendering
func ( m model ) View () tea . View {
switch m . state {
case stateLoading :
return tea . NewView ( m . renderLoading ())
case stateError :
return tea . NewView ( m . renderError ())
case stateSuccess :
return tea . NewView ( m . renderContent ())
default :
return tea . NewView ( "" )
}
}
With Lip Gloss Layout
import " github.com/charmbracelet/lipgloss "
func ( m model ) View () tea . View {
// Create styled boxes
leftBox := lipgloss . NewStyle ().
Border ( lipgloss . NormalBorder ()).
Width ( m . width / 2 ).
Render ( m . leftContent )
rightBox := lipgloss . NewStyle ().
Border ( lipgloss . NormalBorder ()).
Width ( m . width / 2 ).
Render ( m . rightContent )
// Combine horizontally
content := lipgloss . JoinHorizontal ( lipgloss . Top , leftBox , rightBox )
return tea . NewView ( content )
}
Best Practices
Never modify the model in View. Only read from it. // ❌ Bad
func ( m model ) View () tea . View {
m . viewCount ++ // Don't mutate!
return tea . NewView ( m . render ())
}
// ✅ Good
func ( m model ) View () tea . View {
return tea . NewView ( m . render ())
}
Don't Worry About Performance
Break complex views into smaller functions. func ( m model ) View () tea . View {
return tea . NewView ( lipgloss . JoinVertical ( lipgloss . Left ,
m . renderHeader (),
m . renderBody (),
m . renderFooter (),
))
}
func ( m model ) renderHeader () string { /* ... */ }
func ( m model ) renderBody () string { /* ... */ }
func ( m model ) renderFooter () string { /* ... */ }
If rendering is expensive, cache the result in your model. type model struct {
data [] Item
dataHash uint64
cachedView string
}
func ( m model ) View () tea . View {
hash := hashData ( m . data )
if hash != m . dataHash {
m . dataHash = hash
m . cachedView = m . expensiveRender ()
}
return tea . NewView ( m . cachedView )
}
Complete Example
Here’s a full example showing various view features:
package main
import (
" fmt "
" strings "
tea " charm.land/bubbletea/v2 "
" github.com/charmbracelet/lipgloss "
)
type model struct {
width int
height int
inputText string
cursorPos int
}
var (
titleStyle = lipgloss . NewStyle ().
Bold ( true ).
Background ( lipgloss . Color ( "#7D56F4" )).
Padding ( 0 , 1 )
inputStyle = lipgloss . NewStyle ().
Border ( lipgloss . RoundedBorder ()).
BorderForeground ( lipgloss . Color ( "#7D56F4" ))
)
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 . width = msg . Width
m . height = msg . Height
case tea . KeyPressMsg :
switch msg . String () {
case "ctrl+c" :
return m , tea . Quit
case "backspace" :
if len ( m . inputText ) > 0 {
m . inputText = m . inputText [: len ( m . inputText ) - 1 ]
m . cursorPos --
}
default :
if len ( msg . String ()) == 1 {
m . inputText += msg . String ()
m . cursorPos ++
}
}
}
return m , nil
}
func ( m model ) View () tea . View {
var v tea . View
// Build content
title := titleStyle . Render ( "Text Input Demo" )
input := inputStyle . Render ( m . inputText + "_" )
help := "Type to input text. Press ctrl+c to quit."
content := lipgloss . JoinVertical ( lipgloss . Left ,
title ,
"" ,
input ,
"" ,
help ,
)
// Center the content
if m . width > 0 && m . height > 0 {
content = lipgloss . Place ( m . width , m . height ,
lipgloss . Center , lipgloss . Center ,
content ,
)
}
v . SetContent ( content )
// Set view options
v . WindowTitle = "Bubble Tea Demo"
v . MouseMode = tea . MouseModeCellMotion
// Show cursor at input position
if len ( m . inputText ) > 0 {
v . Cursor = tea . NewCursor ( m . cursorPos , 2 )
}
return v
}
func main () {
p := tea . NewProgram ( model {})
if _ , err := p . Run (); err != nil {
fmt . Printf ( "Error: %v " , err )
}
}
Next Steps
Lip Gloss Learn to style your terminal UI with Lip Gloss
Bubbles Use pre-built UI components
Examples See real-world view implementations