| name | charm-stack |
| description | Build terminal UIs with Bubbletea v2, Bubbles v2, Lipgloss v2, and Huh v2. Use when creating TUI applications, interactive forms, styled terminal output, or when user mentions Bubbletea, Bubbles, Lipgloss, Huh, Charm, or TUI development. |
Charm Stack v2 TUI Development
Build terminal UIs using the Charm v2 stack (released Feb 2026):
- Bubble Tea v2 — TUI framework (Elm Architecture)
- Bubbles v2 — Reusable TUI components
- Lip Gloss v2 — Terminal styling & layout
- Huh v2 — Interactive forms & prompts
Installation
go get charm.land/bubbletea/v2
go get charm.land/bubbles/v2
go get charm.land/lipgloss/v2
go get charm.land/huh/v2
Import paths changed in v2 — use charm.land/ not github.com/charmbracelet/.
Your Role: TUI Architect
✅ Use Elm Architecture (Model-Update-View)
✅ Return tea.View from View() (not string)
✅ Use tea.KeyPressMsg (not tea.KeyMsg)
✅ Declare terminal features in View() (not commands)
✅ Use getter/setter methods on Bubbles components
✅ Use functional options for Bubbles constructors
✅ Follow CLY module patterns
❌ Do NOT return string from View()
❌ Do NOT use tea.EnterAltScreen commands
❌ Do NOT use tea.KeyMsg for key matching (use tea.KeyPressMsg)
❌ Do NOT use lipgloss.AdaptiveColor (removed — use LightDark)
❌ Do NOT set Bubbles fields directly (use SetWidth/SetHeight)
❌ Do NOT use old import paths (github.com/charmbracelet/...)
Core Architecture: The Elm Architecture
The Three Functions
type model struct {
cursor int
choices []string
selected map[int]struct{}
}
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.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() tea.View {
s := "Hello, World!\n"
return tea.NewView(s)
}
Message Flow
User Input → Msg → Update → Model → View → Screen
↑ ↓
└────────── tea.Cmd ─────────────────┘
- Messages are immutable events
- Update returns new model (don't mutate)
- Commands run async, generate more messages
- View is a pure function of model state
Bubble Tea v2
Basic Program
package main
import (
"fmt"
"os"
tea "charm.land/bubbletea/v2"
)
type model struct {
choices []string
cursor int
selected map[int]struct{}
}
func initialModel() model {
return model{
choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
selected: make(map[int]struct{}),
}
}
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.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", "space":
if _, ok := m.selected[m.cursor]; ok {
delete(m.selected, m.cursor)
} else {
m.selected[m.cursor] = struct{}{}
}
}
}
return m, nil
}
func (m model) View() tea.View {
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 tea.NewView(s)
}
func main() {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
fmt.Printf("Error: %v", err)
os.Exit(1)
}
}
Declarative View (v2 Key Change)
In v2, View() returns tea.View instead of string. Terminal features are declared on the view struct — no more imperative commands:
func (m model) View() tea.View {
v := tea.NewView(m.renderContent())
v.AltScreen = true
v.MouseMode = tea.MouseModeCellMotion
v.ReportFocus = true
v.WindowTitle = "My App"
if m.showCursor {
v.Cursor = &tea.Cursor{
Position: tea.Position{X: m.cursorX, Y: m.cursorY},
Shape: tea.CursorBlock,
Blink: true,
Color: lipgloss.Green,
}
}
v.KeyboardEnhancements.ReportEventTypes = true
return v
}
View struct fields:
type View struct {
Content string
OnMouse func(msg MouseMsg) Cmd
Cursor *Cursor
BackgroundColor color.Color
ForegroundColor color.Color
WindowTitle string
ProgressBar *ProgressBar
AltScreen bool
ReportFocus bool
DisableBracketedPasteMode bool
MouseMode MouseMode
KeyboardEnhancements KeyboardEnhancements
}
Key Messages (v2 Breaking Change)
Keys are now split into tea.KeyPressMsg and tea.KeyReleaseMsg. Use msg.String() for matching:
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
case "space":
case "shift+enter":
case "ctrl+alt+super+enter":
}
case tea.KeyReleaseMsg:
}
return m, nil
}
Key struct fields:
key.Code — replaces key.Type
key.Text — replaces key.Runes
key.Mod — replaces separate Alt bool
key.BaseCode — key on US PC-101 layout (handy for international keyboards)
key.IsRepeat — auto-repeating held key
key.Keystroke() — always includes modifier info (e.g. "ctrl+shift+alt+a")
Mouse Messages (v2 Breaking Change)
Split into specific types:
case tea.MouseClickMsg:
if msg.Button == tea.MouseLeft {
}
case tea.MouseReleaseMsg:
case tea.MouseWheelMsg:
case tea.MouseMotionMsg:
Mouse mode is set declaratively in View():
func (m model) View() tea.View {
v := tea.NewView("Move that mouse!")
v.MouseMode = tea.MouseModeAllMotion
return v
}
Paste Messages
case tea.PasteMsg:
m.text += msg.Content
case tea.PasteStartMsg:
case tea.PasteEndMsg:
Commands
func checkServer() tea.Msg {
res, err := http.Get("https://example.com")
if err != nil {
return errMsg{err}
}
return statusMsg(res.StatusCode)
}
return m, checkServer
func fetchData() tea.Cmd {
return func() tea.Msg {
return dataMsg{data}
}
}
type tickMsg time.Time
func tick() tea.Cmd {
return tea.Tick(time.Second, func(t time.Time) tea.Msg {
return tickMsg(t)
})
}
return m, tea.Batch(cmdA, cmdB, cmdC)
return m, tea.Println("Status: done")
return m, tea.SetClipboard("copied text")
return m, tea.ReadClipboard()
Background Color Detection
func (m model) Init() tea.Cmd {
return tea.RequestBackgroundColor
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.BackgroundColorMsg:
m.isDark = msg.IsDark()
m.styles = newStyles(msg.IsDark())
}
return m, nil
}
Environment Variables (v2 new!)
Bubble Tea sends tea.EnvMsg at startup — essential for SSH apps:
case tea.EnvMsg:
m.term = msg.Getenv("TERM")
Color Profile Detection
case tea.ColorProfileMsg:
m.colorProfile = msg.Profile
Keyboard Enhancements Detection
case tea.KeyboardEnhancementsMsg:
if msg.SupportsKeyDisambiguation() {
}
Program Options
p := tea.NewProgram(model,
tea.WithWindowSize(80, 24),
tea.WithColorProfile(profile),
tea.WithEnvironment(envs),
tea.WithInput(ttyIn),
tea.WithOutput(ttyOut),
)
ttyIn, ttyOut, err := tea.OpenTTY()
Debugging
if len(os.Getenv("DEBUG")) > 0 {
f, err := tea.LogToFile("debug.log", "debug")
if err != nil {
fmt.Println("fatal:", err)
os.Exit(1)
}
defer f.Close()
}
Bubbles v2
All Bubbles updated for v2 with consistent patterns:
Import Paths
import "charm.land/bubbles/v2/spinner"
import "charm.land/bubbles/v2/textinput"
import "charm.land/bubbles/v2/textarea"
import "charm.land/bubbles/v2/list"
import "charm.land/bubbles/v2/table"
import "charm.land/bubbles/v2/viewport"
import "charm.land/bubbles/v2/progress"
import "charm.land/bubbles/v2/help"
import "charm.land/bubbles/v2/paginator"
import "charm.land/bubbles/v2/filepicker"
import "charm.land/bubbles/v2/timer"
import "charm.land/bubbles/v2/stopwatch"
import "charm.land/bubbles/v2/cursor"
import "charm.land/bubbles/v2/key"
v2 Universal Changes
-
Functional options for constructors:
vp := viewport.New(viewport.WithWidth(80), viewport.WithHeight(24))
sw := stopwatch.New(stopwatch.WithInterval(500 * time.Millisecond))
t := timer.New(30*time.Second, timer.WithInterval(100*time.Millisecond))
-
Getters/setters instead of exported fields:
vp.SetWidth(40)
fmt.Println(vp.Width())
-
DefaultKeyMap is a function:
km := textinput.DefaultKeyMap()
km := textarea.DefaultKeyMap()
km := paginator.DefaultKeyMap()
-
Light/dark styles:
h := help.New()
h.Styles = help.DefaultStyles(isDark)
h.Styles = help.DefaultDarkStyles()
h.Styles = help.DefaultLightStyles()
Spinner
import "charm.land/bubbles/v2/spinner"
type model struct {
spinner spinner.Model
}
func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}
}
func (m model) Init() tea.Cmd {
return m.spinner.Tick()
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
func (m model) View() tea.View {
return tea.NewView(m.spinner.View() + " Loading...")
}
Text Input
import "charm.land/bubbles/v2/textinput"
type model struct {
input textinput.Model
}
func initialModel() model {
ti := textinput.New()
ti.Placeholder = "Type something..."
ti.Focus()
ti.SetWidth(40)
return model{input: ti}
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "enter":
case "ctrl+c":
return m, tea.Quit
}
}
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
return m, cmd
}
func (m model) View() tea.View {
return tea.NewView(m.input.View())
}
v2 style changes for textinput:
styles := textinput.DefaultStyles(isDark)
ti.SetStyles(styles)
Text Area
import "charm.land/bubbles/v2/textarea"
ta := textarea.New()
ta.Placeholder = "Write something..."
ta.Focus()
styles := textarea.DefaultStyles(isDark)
ta.Column()
ta.ScrollYOffset()
ta.MoveToBeginning()
ta.MoveToEnd()
ta.SetCursorColumn(col)
List
import "charm.land/bubbles/v2/list"
type item struct {
title, desc string
}
func (i item) Title() string { return i.title }
func (i item) Description() string { return i.desc }
func (i item) FilterValue() string { return i.title }
func initialModel() model {
items := []list.Item{
item{title: "Raspberry Pi", desc: "A tiny computer"},
item{title: "Arduino", desc: "A microcontroller"},
}
l := list.New(items, list.NewDefaultDelegate(), 20, 14)
l.Title = "My Items"
return model{list: l}
}
Table
import "charm.land/bubbles/v2/table"
columns := []table.Column{
{Title: "Name", Width: 20},
{Title: "Age", Width: 5},
}
rows := []table.Row{
{"Alice", "25"},
{"Bob", "30"},
}
t := table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
)
t.SetWidth(40)
t.SetHeight(10)
Viewport
import "charm.land/bubbles/v2/viewport"
vp := viewport.New(viewport.WithWidth(80), viewport.WithHeight(24))
vp.SetContent("long content here...")
vp.SoftWrap = true
vp.LeftGutterFunc = func(info viewport.GutterContext) string {
if info.Soft {
return " | "
}
if info.Index >= info.TotalLines {
return " ~ | "
}
return fmt.Sprintf("%4d | ", info.Index+1)
}
vp.SetHighlights(regexp.MustCompile("error").FindAllStringIndex(vp.GetContent(), -1))
vp.HighlightNext()
vp.HighlightPrevious()
vp.ClearHighlights()
vp.StyleLineFunc = func(line int, content string) string {
}
Progress
import "charm.land/bubbles/v2/progress"
p := progress.New(
progress.WithColors(lipgloss.Color("#FF0000"), lipgloss.Color("#0000FF")),
progress.WithScaled(true),
progress.WithDefaultBlend(),
)
p.SetWidth(40)
p := progress.New(progress.WithColors(lipgloss.Color("#FF7CCB")))
p := progress.New(progress.WithColorFunc(func(total, current float64) color.Color {
}))
Help
import "charm.land/bubbles/v2/help"
import "charm.land/bubbles/v2/key"
type keyMap struct {
Up key.Binding
Down key.Binding
Quit key.Binding
}
func (k keyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Up, k.Down, k.Quit}
}
func (k keyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{{k.Up, k.Down}, {k.Quit}}
}
var keys = keyMap{
Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/k", "up")),
Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "down")),
Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")),
}
h := help.New()
h.SetWidth(80)
h.Styles = help.DefaultStyles(isDark)
Key Bindings
import "charm.land/bubbles/v2/key"
binding := key.NewBinding(
key.WithKeys("k", "up"),
key.WithHelp("↑/k", "move up"),
)
case tea.KeyPressMsg:
switch {
case key.Matches(msg, keys.Up):
case key.Matches(msg, keys.Down):
}
Lip Gloss v2
Import
import "charm.land/lipgloss/v2"
Styles
var style = lipgloss.NewStyle().
Bold(true).
Italic(true).
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
PaddingTop(2).
PaddingLeft(4).
Width(22)
lipgloss.Println(style.Render("Hello, kitty"))
Colors (v2 Change: colors are now image/color.Color)
lipgloss.Color("#FF0000")
lipgloss.Color("86")
lipgloss.Color("5")
lipgloss.Red
lipgloss.Green
lipgloss.BrightCyan
lipgloss.RGBColor{R: 255, G: 0, B: 0}
Adaptive Colors (v2 Breaking Change)
lipgloss.AdaptiveColor is removed. Use LightDark:
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.BackgroundColorMsg:
m.styles = newStyles(msg.IsDark())
}
return m, nil
}
func newStyles(isDark bool) styles {
lightDark := lipgloss.LightDark(isDark)
return styles{
title: lipgloss.NewStyle().Foreground(lightDark(
lipgloss.Color("#333333"),
lipgloss.Color("#FAFAFA"),
)),
}
}
hasDarkBG, _ := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
lightDark := lipgloss.LightDark(hasDarkBG)
myColor := lightDark(lipgloss.Color("#333"), lipgloss.Color("#FFF"))
import "charm.land/lipgloss/v2/compat"
color := compat.AdaptiveColor{
Light: lipgloss.Color("#f1f1f1"),
Dark: lipgloss.Color("#cccccc"),
}
Color Utilities
dark := lipgloss.Darken(c, 0.5)
light := lipgloss.Lighten(c, 0.35)
comp := lipgloss.Complementary(c)
alpha := lipgloss.Alpha(c, 0.2)
colors := lipgloss.Blend1D(10, color1, color2)
colors := lipgloss.Blend2D(80, 24, 45.0, color1, color2, color3)
Borders
var style = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
BorderTop(true).
BorderBottom(true).
BorderLeft(true).
BorderRight(true)
s := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForegroundBlend(lipgloss.Color("#FF0000"), lipgloss.Color("#0000FF"))
Underline Styles (v2 new!)
s := lipgloss.NewStyle().
UnderlineStyle(lipgloss.UnderlineCurly).
UnderlineColor(lipgloss.Color("#FF0000"))
Hyperlinks (v2 new!)
s := lipgloss.NewStyle().
Foreground(lipgloss.Color("#7B2FBE")).
Hyperlink("https://charm.land")
lipgloss.Println(s.Render("Visit Charm"))
Layout
lipgloss.JoinHorizontal(lipgloss.Bottom, blockA, blockB)
lipgloss.JoinVertical(lipgloss.Center, blockA, blockB)
lipgloss.Place(80, 24, lipgloss.Center, lipgloss.Center, content)
lipgloss.PlaceHorizontal(80, lipgloss.Center, content)
lipgloss.PlaceVertical(24, lipgloss.Bottom, content)
w, h := lipgloss.Size(block)
width := lipgloss.Width(block)
height := lipgloss.Height(block)
wrapped := lipgloss.Wrap(styledText, 40, " ")
Compositing (v2 new!)
a := lipgloss.NewLayer(pickles).X(4).Y(2).Z(1)
b := lipgloss.NewLayer(bitterMelon).X(22).Y(1)
c := lipgloss.NewLayer(sriracha).X(11).Y(7)
output := compositor.Compose(a, b, c).Render()
Tables (lipgloss sub-package)
import "charm.land/lipgloss/v2/table"
t := table.New().
Border(lipgloss.RoundedBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
StyleFunc(func(row, col int) lipgloss.Style {
switch {
case row == table.HeaderRow:
return headerStyle
case row%2 == 0:
return evenRowStyle
default:
return oddRowStyle
}
}).
Headers("LANGUAGE", "FORMAL", "INFORMAL").
Rows(rows...)
lipgloss.Println(t)
Lists and Trees (lipgloss sub-packages)
import "charm.land/lipgloss/v2/list"
import "charm.land/lipgloss/v2/tree"
l := list.New("A", "B", "C").
Enumerator(list.Roman).
EnumeratorStyle(enumStyle).
ItemStyle(itemStyle)
t := tree.Root(".").
Child("macOS", "Linux", "BSD").
Enumerator(tree.RoundedEnumerator)
lipgloss.Println(l)
lipgloss.Println(t)
Printing (v2 Change: use lipgloss writers for standalone)
lipgloss.Print(s)
lipgloss.Println(s)
lipgloss.Printf("hello %s", styled)
lipgloss.Fprint(os.Stderr, s)
lipgloss.Sprint(s)
Huh v2 (Forms)
Import
import "charm.land/huh/v2"
Standalone Form
var name string
var confirmed bool
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("What's your name?").
Value(&name).
Validate(func(s string) error {
if s == "" {
return errors.New("name required")
}
return nil
}),
huh.NewConfirm().
Title("Continue?").
Value(&confirmed),
),
)
err := form.Run()
Field Types
huh.NewInput().Title("Name").Placeholder("Enter name...").Value(&name)
huh.NewText().Title("Description").CharLimit(400).Value(&desc)
huh.NewSelect[string]().
Title("Pick one").
Options(
huh.NewOption("Option A", "a"),
huh.NewOption("Option B", "b"),
).
Value(&choice)
huh.NewMultiSelect[string]().
Title("Pick many").
Options(huh.NewOptions("A", "B", "C")...).
Limit(2).
Value(&choices)
huh.NewConfirm().Title("Sure?").Affirmative("Yes!").Negative("No.").Value(&ok)
Single Field Shorthand
var name string
huh.NewInput().Title("Name?").Value(&name).Run()
Dynamic Forms
huh.NewSelect[string]().
Value(&state).
TitleFunc(func() string {
if country == "US" { return "State" }
return "Province"
}, &country).
OptionsFunc(func() []huh.Option[string] {
return huh.NewOptions(fetchStates(country)...)
}, &country)
Huh in Bubble Tea
type model struct {
form *huh.Form
}
func newModel() model {
return model{
form: huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Key("class").
Options(huh.NewOptions("Warrior", "Mage", "Rogue")...).
Title("Choose your class"),
),
),
}
}
func (m model) Init() tea.Cmd {
return m.form.Init()
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
form, cmd := m.form.Update(msg)
if f, ok := form.(*huh.Form); ok {
m.form = f
}
return m, cmd
}
func (m model) View() tea.View {
if m.form.State == huh.StateCompleted {
class := m.form.GetString("class")
return tea.NewView(fmt.Sprintf("You chose: %s", class))
}
return tea.NewView(m.form.View())
}
Themes & Accessibility
form.WithTheme(huh.ThemeDracula())
form.WithAccessible(os.Getenv("ACCESSIBLE") != "")
Spinner
import "charm.land/huh/v2/spinner"
err := spinner.New().
Title("Processing...").
Action(doWork).
Run()
CLY Module Integration
Simple TUI Module
package mymodule
import (
"fmt"
"github.com/spf13/cobra"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
)
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "mymodule",
Short: "My TUI module",
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel())
_, err := p.Run()
return err
}
type model struct {
}
func initialModel() model {
return model{}
}
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.KeyPressMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() tea.View {
return tea.NewView("Hello from my module!")
}
Full-screen TUI Module
func (m model) View() tea.View {
v := tea.NewView(m.renderContent())
v.AltScreen = true
return v
}
Using Shared Styles
import "github.com/yurifrl/cly/pkg/style"
fmt.Println(style.TitleStyle.Render("Title"))
fmt.Println(style.GreenStyle.Render("Success"))
v1 to v2 Migration Quick Reference
| v1 | v2 |
|---|
import tea "github.com/charmbracelet/bubbletea" | import tea "charm.land/bubbletea/v2" |
import "github.com/charmbracelet/bubbles/..." | import "charm.land/bubbles/v2/..." |
import "github.com/charmbracelet/lipgloss" | import "charm.land/lipgloss/v2" |
import "github.com/charmbracelet/huh" | import "charm.land/huh/v2" |
View() string | View() tea.View |
return "content" | return tea.NewView("content") |
tea.KeyMsg | tea.KeyPressMsg / tea.KeyReleaseMsg |
key.Type, key.Runes | key.Code, key.Text |
msg.Alt (bool) | key.Mod |
tea.MouseMsg | tea.MouseClickMsg / tea.MouseWheelMsg / etc. |
tea.EnterAltScreen (Cmd) | v.AltScreen = true (View) |
tea.EnableMouseCellMotion (Cmd) | v.MouseMode = tea.MouseModeCellMotion (View) |
lipgloss.AdaptiveColor{Light, Dark} | lipgloss.LightDark(isDark)(light, dark) |
fmt.Println(styled) | lipgloss.Println(styled) |
viewport.New(w, h) | viewport.New(viewport.WithWidth(w), viewport.WithHeight(h)) |
vp.Width = 40 | vp.SetWidth(40) |
vp.Width (field) | vp.Width() (method) |
textinput.DefaultKeyMap (var) | textinput.DefaultKeyMap() (func) |
spinner.Tick() (pkg func) | m.spinner.Tick() (method) |
viewport.HighPerformanceRendering | Removed (not needed) |
ti.PromptStyle | styles.Focused.Prompt |
ta.FocusedStyle | ta.Styles.Focused |
progress.WithGradient(a, b) | progress.WithColors(a, b) |
progress.WithDefaultGradient() | progress.WithDefaultBlend() |
space key returns " " | space key returns "space" |