Bubble Tea beta 5 is here!
We're excited to share the new Bubble Tea v2 beta 5 release with you! In this release, we focused on refining the View
API, making it more declarative with a single source of truth for view-related properties.
Summary
All the changes since Beta 4 are to supporting a more declarative View
type. This might sound like a lot but it's fairly simple in practice. This is what it looks like now:
func (m model) View() tea.View {
v := tea.NewView("Hi, mom!")
v.AltScreen = true
v.WindowTitle = "It’s all about Mom today"
}
Why the change?
The general impetus for doing this is to eliminate the possibility of race conditions and greatly improve view performance. For example, the altscreen was previously triggered via a Cmd
, which would not necessarily hit at the same moment the view was being rendered.
What else happened?
We've removed many view-based startup options and commands in favor of a more declarative approach using the View
type. For example, WithMouseCellMotion()
is now a property on View
, i.e. view.MouseMode = tea.MouseModeCellMotion
.
Now, the View()
method returns a View
struct that encapsulates all view-related properties. This change simplifies the API and makes it more intuitive to use with a single point of configuration for views.
The View API
Previously, Bubble Tea used functional options and commands to configure various view-related properties. This approach was flexible but led to a fragmented API surface. Should I use WithAltScreen()
or EnterAltScreen
? Is there a difference between them? Why can't I enable focus reporting with WithReportFocus()
but not via a command?
These questions and inconsistencies led us to rethink how we handle view configuration. The new View
struct consolidates all view-related properties, making it easier to understand and use with a single source of truth. I.e., this is how your view looks like, and these are its properties.
type View struct {
Layer Layer // Layer represents the content of the view
Cursor *Cursor // Position, style, color (nil = hidden)
BackgroundColor color.Color // Terminal default background color
ForegroundColor color.Color // Terminal default foreground color
WindowTitle string // Window title
ProgressBar *ProgressBar // Progress bar (nil = no progressbar)
AltScreen bool // Alternate screen buffer (fullscreen mode)
MouseMode MouseMode // Mouse event mode
ReportFocus bool // Focus/blur events
DisableBracketedPasteMode bool // Bracketed paste
DisableKeyEnhancements bool // Keyboard enhancements
KeyReleases bool // Key release events
UniformKeyLayout bool // Uniform key layout
}
Options and Commands
We've removed many of the view-related options and commands that were previously set using functional options or commands. Instead, these properties are now part of the View
struct returned by the View()
method.
Removed options include:
WithAltScreen()
→View.AltScreen = true
WithMouseCellMotion()
→View.MouseMode = tea.MouseModeCellMotion
WithMouseAllMotion()
→View.MouseMode = tea.MouseModeAllMotion
WithReportFocus()
→View.ReportFocus = true
WithKeyReleases()
→View.KeyReleases = true
WithUniformKeyLayout()
→View.UniformKeyLayout = true
WithoutBracketedPaste()
→View.DisableBracketedPasteMode = true
WithInputTTY()
→ useOpenTTY()
and set input/output manually
Removed commands include:
EnterAltScreen
/ExitAltScreen
→View.AltScreen
SetBackgroundColor()
→View.BackgroundColor
SetForegroundColor()
→View.ForegroundColor
SetCursorColor()
→View.Cursor.Color
SetWindowTitle()
→View.WindowTitle
EnableMouseCellMotion
/EnableMouseAllMotion
/DisableMouse
→View.MouseMode
Model Interface
The Model
interface has been updated to make View()
return a View
struct instead of a string. We know that a string is often more convenient and easier to work with, however, due to the number of view-related options we wanted to support, we felt it was best to encapsulate them in a dedicated struct.
You can still use strings for your views of course! You just need to wrap them in a NewView()
call, or use view.SetContent(yourString)
.
// Before
type Model interface {
Init() Cmd
Update(Msg) (Model, Cmd)
View() string
// Or...
View() (string, *Cursor)
}
// After
type Model interface {
Init() Cmd
Update(Msg) (Model, Cmd)
View() View
}
Example?
Let's get to the fun part! Here's a simple example that displays a text in the center of the screen using the alt-screen buffer.
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
)
type model struct {
width, height int
}
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:
return m, tea.Quit
}
return m, nil
}
func (m model) View() tea.View {
var v tea.View
content := lipgloss.NewStyle().
Width(m.width).
Height(m.height).
AlignHorizontal(lipgloss.Center).
AlignVertical(lipgloss.Center).
Foreground(lipgloss.Cyan).
Render(" Bubble Tea Beta 5! ")
v.AltScreen = true
v.SetContent(content)
return v
}
func main() {
p := tea.NewProgram(model{})
if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Alas, there's been an error: %v", err)
}
}
Like details?
Here’s the full changelog since v2.0.0-beta.4
Changelog
New!
Fixed
Docs
Other stuff
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.