Releases: charmbracelet/bubbles
v2.1.0
Shrink ’n’ grow your textareas
The update adds a new feature to automatically resize your textarea vertically as its content changes.
ta := textarea.New()
ta.DynamicHeight = true // Enable dynamic resizing
ta.MinHeight = 3 // Minimum visible rows
ta.MaxHeight = 10 // Maximum visible rows
ta.MaxContentHeight = 20 // Maximum rows of contentPiece of cake, right?
Enjoy! 💘
Changelog
New!
- f1daacf: feat(textarea): dynamic height (#910) (@meowgorithm)
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
v2.0.0
Bubbles v2 is here! 🫧
We're thrilled to share Bubbles v2 with you! This release accompanies Bubble Tea v2 and Lip Gloss v2 and brings a ton of consistency, new features, and quality-of-life improvements across every component. Catch 'em all:
go get charm.land/bubbletea/v2
go get charm.land/bubbles/v2
go get charm.land/lipgloss/v2You can also check the Upgrade Guide for more info.
There are a lot of changes in here, but we've found upgrading pretty easy, especially with a linter. Read on for the full breakdown!
Note
When in doubt, check the examples for reference — they've all been updated for v2.
🏠 New Home
Bubbles v2 now lives at charm.land:
import "charm.land/bubbles/v2"All sub-packages follow the same pattern: charm.land/bubbles/v2/viewport, charm.land/bubbles/v2/list, etc.
🎨 Light and Dark Styles
Some Bubbles, like help, offer default styles for both light and dark backgrounds. Since Lip Gloss v2 removes AdaptiveColor, choosing light or dark is now a manual process. You've got a couple of options.
🎩 The Best Way
Have Bubble Tea query the background color for you. This properly queries the correct inputs and outputs, and happens in lockstep with your application:
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.help.Styles = help.DefaultStyles(msg.IsDark())
return m, nil
}
// ...
}If you're using Wish you must do it this way to get the background color of the client.
🤠 The Quick Way
Use the compat package in Lip Gloss. It's less recommended because it contains blocking I/O that operates independently of Bubble Tea, and when used with Wish it won't detect the client's background:
import "charm.land/lipgloss/v2/compat"
var hasDarkBG = compat.HasDarkBackground()
h := help.New()
h.Styles = help.DefaultStyles(hasDarkBG)👀 Or Just Pick One
h.Styles = help.DefaultLightStyles() // light mode!
h.Styles = help.DefaultDarkStyles() // jk dark modeThis pattern applies to help, list, textarea, and textinput.
🔑 The Init You Know and Love is Back
After experimenting with a few different forms of Init during the alphas, we decided the v1 signature was the right call after all. Init was a bit too redundant for our tastes given that initialization already happens in New:
func (m Model) Init() tea.Cmd✨ The Big Highlights
Getters and Setters Everywhere
All components now use getter/setter methods instead of exported Width and Height fields. This lets us do internal bookkeeping when things change, and it makes the API consistent across every Bubble:
// Before
vp.Width = 40
fmt.Println(vp.Width)
// After
vp.SetWidth(40)
fmt.Println(vp.Width())Affected: filepicker, help, progress, table, textinput, viewport.
Functional Options
Constructors now use the functional options pattern instead of positional args or separate constructor functions:
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))DefaultKeyMap is a Function Now
All DefaultKeyMap package-level variables are now functions, so you get fresh values every time:
km := textinput.DefaultKeyMap() // was textinput.DefaultKeyMap
km := textarea.DefaultKeyMap() // was textarea.DefaultKeyMap
km := paginator.DefaultKeyMap() // was paginator.DefaultKeyMapReal Cursor Support 🖱️
Both textarea and textinput now support real terminal cursors! The feature is opt-in, so by default your programs will continue to use the easy-breezy virtual cursor. Set VirtualCursor to false and use Model.Cursor() for the real deal. Check out the textarea and textinput examples to see it in action.
Cleaned House 🧹
All previously deprecated symbols have been removed:
NewModelvariants — just useNewspinner.Tick()— useModel.Tick()insteadpaginator.UsePgUpPgDownKeysand friends — customizeKeyMapdirectlyfilepicker.DefaultStylesWithRenderer()— Lip Gloss is pure now, useDefaultStyles()viewport.HighPerformanceRendering— no longer neededruneutilandmemoizationpackages moved tointernal/(they were never meant for public use anyway)
What's Changed: the Laundry List
🔮 Cursor
Model.Blinkrenamed toModel.IsBlinkedfor clarityModel.BlinkCmd()renamed toModel.Blink()- Each cursor now gets a unique ID
📂 Filepicker
DefaultStylesWithRenderer()removed — Lip Gloss is pure now, so just useDefaultStyles()Model.Heightbroken intoSetHeight(int)/Height() int
❓ Help
Model.Widthbroken intoSetWidth(int)/Width() int- New
DefaultStyles(isDark bool),DefaultDarkStyles(), andDefaultLightStyles() - Defaults to dark background styles out of the box
🥕 List
DefaultStyles()andNewDefaultItemStyles()now take anisDark boolparameterStyles.FilterPromptandStyles.FilterCursorhave been consolidated intoStyles.Filter(atextinput.Styles)GlobalIndexhelper added
📄 Paginator
DefaultKeyMapvariable →DefaultKeyMap()function- Deprecated fields (
UsePgUpPgDownKeys,UseLeftRightKeys, etc.) removed — customizeKeyMapdirectly
🌈 Progress
This one got the biggest makeover!
- Complete color API overhaul:
WithGradient/WithScaledGradient→WithColors(...color.Color)— pass 2+ colors for blendingWithSolidFill(string)→WithColors(color)— pass a single color for a solid fillWithDefaultGradient()→WithDefaultBlend()- New
WithScaled(bool)to scale the blend to fit only the filled portion - New
WithColorFunc(func(total, current float64) color.Color)for fully dynamic coloring WithColorProfileremoved — Bubble Tea handles this automatically now
Model.FullColorandModel.EmptyColorchanged fromstringtoimage/color.ColorModel.Widthbroken intoSetWidth(int)/Width() intModel.Updatenow returnsModelinstead oftea.Model- Improved blend algorithm with support for multiple color stops — special thanks to the legendary @lrstanley!
🌀 Spinner
Tick()package-level function removed — useModel.Tick()instead
⏱️ Stopwatch
NewWithInterval(d)removed — useNew(WithInterval(d))instead- Debounced tick messages
🔢 Table
Model.Width/Model.Heightreplaced with getter/setter methods- Uses
ansi.Truncateinstead ofrunewidth.Truncate - Fixed a critical out-of-bounds cursor bug — thanks @s0ders!
✏️ Textarea
The big change here is real cursor support — but that's opt-in, so by default your programs will keep using the virtual cursor.
DefaultKeyMapvariable →DefaultKeyMap()function- New
PageUp/PageDownkey bindings Model.FocusedStyle/Model.BlurredStyle→Model.Styles.Focused/Model.Styles.BlurredStyletype renamed toStyleState; newStylesstruct groupsFocused,Blurred, andCursorModel.SetCursorrenamed toModel.SetCursorColumnModel.Cursoris nowfunc() *tea.Cursorfor real cursor supportModel.VirtualCursorbool added — set tofalsewhen using a real cursor- New
DefaultStyles(isDark bool),DefaultDarkStyles(),DefaultLightStyles() - New methods:
Column(),ScrollYOffset(),ScrollPosition(),MoveToBeginning(),MoveToEnd() - Focus status now passed to
SetPromptFunc
📜 Textinput
Most of the changes here bring textinput to parity with textarea, including real cursor support. Styling has been consolidated into a Styles struct with Focused and Blurred states:
DefaultKeyMapvariable →DefaultKeyMap()functionModel.Widthbroken intoSetWidth(int)/Width() intModel.PromptStyle→StyleState.PromptModel.TextStyle→StyleState.TextModel.PlaceholderStyle→StyleState.PlaceholderModel.CompletionStyle→StyleState.SuggestionModel.Cursoris nowfunc() *tea.Cursorfor real cursor supportModel.VirtualCursor()/SetVirtualCursor(bool)addedModel.Styles()/SetStyles(Styles)added- New
DefaultStyles(isDark bool),DefaultDarkStyles(),DefaultLightStyles() - Exposed matched suggestions and suggestion index
⏲️ Timer
NewWithInterval(timeout, interval)removed — useNew(timeout, WithInterval(interval))- Debounced tick messages
📦 Viewport
viewport got a ton of love in v2. Let's dive in!
Breaking changes:
New(width, height int)→New(...Option)withWithWidth/WithHeightModel.Width,Model.Height,Model.YOffsetreplaced with getter/setter methodsHighPerformanceRenderingremoved
Shiny new features:
You can now scroll horizontally with the left and right arrow keys, and set up a custom gutter column for things like line numbers:
vp := viewport.New()
vp.SetContent("hello world")
// Show line numbers:
vp.LeftGutterFunc = func(info viewport.GutterContext) ...v1.0.0
This is just an honorary release of Bubbles v1. Stay tuned for the next major version 🫧
Changelog
Fixed
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
v0.21.1
Changelog
New!
- dff42dd: feat: update keybindings in list setSize method (@Broderick-Westrope)
Fixed
- c376ce3: fix(cursor): fix data race on blinkTag (#784) (@DryHumour)
- 11d52ca: fix(table): preventing cursor from being out-of-bounds. (@s0ders)
- 49ff5c0: fix(textinput): improve placeholder (#768) (@caarlos0)
- 7c44f63: v1: fix(list): ensure correct cursor positions with page/cursor methods (#831) (@lrstanley)
Docs
- 7fcf75d: docs(readme): update footer image and copyright date (@meowgorithm)
- d4feefe: docs: remove Charm Cloud reference (#785) (@ShalokShalom)
Other stuff
- daab808: ci: sync dependabot config (#786) (@charmcli)
- 4b2d311: ci: sync dependabot config (#835) (@charmcli)
- 8562e90: ci: sync golangci-lint config (#781) (@github-actions[bot])
- f54a125: test(table): improve table unit tests (#601) (@Broderick-Westrope)
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
v2.0.0-rc.1
This is the first step towards the final v2.0.0 release. In this release, we've introduced a variety of changes to expose more functionality from Bubbles and have fixed numerous issues!
This release now uses the charm.land/* domain for imports 🖥️
go get charm.land/bubbles/v2@v2.0.0-rc.1
Progress 🌈
Special thanks to the legendary @lrstanley for his outstanding work on improving the blend algorithm in the Progress Bubble.
Textarea ✍️
You can now use PageUp, PageDown, ScrollYOffset, ScrollPosition, MoveToBeginning , and MoveToEnd, along with numerous bug fixes!
Viewport 📜
The Viewport Bubble now supports horizontal mouse wheel scrolling, thanks to @UnseenBook.
Table 🔢
This release includes a critical out-of-bound fix by @s0ders.
Changelog
New!
- faa17cb: feat(progress): support multiple stops and improved blend algorithm (#838) (@lrstanley)
- bc03e71: feat(textarea): add PageUp & PageDown support (#844) (@lrstanley)
- c690ea5: feat(textarea): add ScrollYOffset and ScrollPosition methods (@lrstanley)
- a4c42b5: feat(textarea): add focus status to setPromptFunc (#797) (@meowgorithm)
- 696244a: feat(textarea): expose MoveToBegin and MoveToEnd methods (#809) (@aymanbagabas)
- 1e2ffbb: feat(textarea): get the word under the cursor (#814) (@caarlos0)
- a0a432e: feat(viewport): horizontal scroll with mouse wheel (@UnseenBook)
- ea344ab: feat(viewport): horizontal scroll with mouse wheel (#761) (@UnseenBook)
- dff42dd: feat: update keybindings in list setSize method (@Broderick-Westrope)
Fixed
- f2434c3: Revert "fix(viewport): normalize method names" (@caarlos0)
- c376ce3: fix(cursor): fix data race on blinkTag (#784) (@DryHumour)
- ba5555a: fix(cursor): set ID on virutal cursors (@meowgorithm)
- c2223a7: fix(list): ensure correct cursor positions with page/cursor methods (#831) (#837) (@lrstanley)
- 11d52ca: fix(table): preventing cursor from being out-of-bounds. (@s0ders)
- b3f0c9e: fix(textarea): cursorline now fills the line when a placeholder is present (@meowgorithm)
- 50038eb: fix(textarea): ensure cursor is always in view (#840) (@lrstanley)
- c4068c6: fix(textarea): ensure viewport content is set during update (@aymanbagabas)
- 8b55efb: fix(textarea): placeholder with chinese chars (#767) (@caarlos0)
- bb1d1d2: fix(textarea): suppress blink messages when real cursor is active (@meowgorithm)
- 86f3326: fix(textarea): update tests to reflect changes in textarea content (@aymanbagabas)
- efdc0e8: fix(textarea): use pointer receiver for Model methods (@aymanbagabas)
- 49ff5c0: fix(textinput): improve placeholder (#768) (@caarlos0)
- c7f889e: fix(viewport): normalize method names (@caarlos0)
- 39668ec: fix(viewport): normalize method names (#763) (@caarlos0)
- bd2a5b0: fix: golangci-lint 2 fixes (#769) (@caarlos0)
- b1cef26: fix: normalize yoffset (@caarlos0)
- 84fd71d: fix: use charm.land import path for bubbletea (@aymanbagabas)
- 7c44f63: v1: fix(list): ensure correct cursor positions with page/cursor methods (#831) (@lrstanley)
Docs
- 7fcf75d: docs(readme): update footer image and copyright date (@meowgorithm)
- d4feefe: docs: remove Charm Cloud reference (#785) (@ShalokShalom)
Other stuff
- 6f4a536: Update viewport/viewport.go (@caarlos0)
- daab808: ci: sync dependabot config (#786) (@charmcli)
- 4b2d311: ci: sync dependabot config (#835) (@charmcli)
- cce8481: ci: sync golangci-lint config (#770) (@github-actions[bot])
- 8562e90: ci: sync golangci-lint config (#781) (@github-actions[bot])
- b531de8: fix!(textarea): virtual cursor blink (@meowgorithm)
- 8e84f33: fix!(textinput): cursor fixes and improvements (@meowgorithm)
- d42b7c4: fix(textarea/tests): update tests per API changes (@meowgorithm)
- 56bbc4a: fix(textinput,textarea): don't draw real cusor when blurred (@meowgorithm)
- 538d39c: refactor(help): use setter/getter for help width (@aymanbagabas)
- 7445f97: refactor(viewport): softwrap; improve perf; various bug fixes (#823) (@lrstanley)
- da0b892: refactor: migrate imports to charm.land/lipgloss (@aymanbagabas)
- 07735d1: refactor: update module path to charm.land (@aymanbagabas)
- e8fcfc5: refactor: use msg.Content for PasteMsg in textinput and textarea (@aymanbagabas)
- f54a125: test(table): improve table unit tests (#601) (@Broderick-Westrope)
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
v0.21.0
Viewport improvements
Finally, viewport finally has horizontal scrolling ✨!1
To enable it, use SetHorizontalStep (default in v2 will be 6).
You can also scroll manually with ScrollLeft and ScrollRight, and use
SetXOffset to scroll to a specific position (or 0 to reset):
vp := viewport.New()
vp.SetHorizontalStep(10) // how many columns to scroll on each key press
vp.ScrollRight(30) // pan 30 columns to the right!
vp.ScrollLeft(10) // pan 10 columns to the left!
vp.SetXOffset(0) // back to the left edgeTo make the API more consistent, vertical scroll functions were also renamed,
and the old ones were deprecated (and will be removed in v2):
// Scroll n lines up/down:
func (m Model) LineUp(int) // deprecated
func (m Model) ScrollUp(int) // new!
func (m Model) LineDown(int) // deprecated
func (m Model) ScrollDown(int) // new!
// Scroll half page up/down:
func (m Model) HalfViewUp() []string // deprecated
func (m Model) HalfPageUp() []string // new!
func (m Model) HalfViewDown() []string // deprecated
func (m Model) HalfPageDown() []string // new!
// Scroll a full page up/down:
func (m Model) ViewUp(int) []string // deprecated
func (m Model) PageUp(int) []string // new!
func (m Model) ViewDown(int) []string // deprecated
func (m Model) PageDown(int) []string // new!Note
In v2, these functions will not return lines []string anymore, as it is no
longer needed due to HighPerformanceRendering being deprecated as well.
Other improvements
The list bubble got a couple of new functions: SetFilterText,
SetFilterState, and GlobalIndex - which you can use to get the index of the
item in the unfiltered, original item list.
On textinput, you can now get the matched suggestions and more with
MatchedSuggestions and CurrentSuggestionIndex.
To put the cherry on top, this release also includes numerous bug fixes.
You can read about each of them in the linked commits/PRs below.
Changelog
New Features
- d019ed3: feat(list): add SetFilterText and SetFilterState (#335) (@taigrr)
- 171a9d6: feat(list): implement GlobalIndex helper (#574) (@nobe4)
- 4382fdf: feat(textinput): expose matched suggestions and index (@luevano)
- 2d53a61: feat(viewport): horizontal scroll (#240) (@tty2)
- ea344ab: feat(viewport): horizontal scroll with mouse wheel (#761) (@UnseenBook)
Bug fixes
- f2434c3: Revert "fix(viewport): normalize method names" (@caarlos0)
- 8101a34: fix(ci): add lint and lint-sync workflows and update golangci.yml (#651) (@aymanbagabas)
- 54f28b6: fix(filepicker): properly scrolling filepicker (#753) (@caarlos0)
- 81d444a: fix(help): wrong full help sep rendering (@luevano)
- f439d83: fix(textarea): max height should not determine max lines (@meowgorithm)
- 8b55efb: fix(textarea): placeholder with chinese chars (#767) (@caarlos0)
- 8624776: fix(textinput): slicing outside cap (#532) (@MikaelFangel)
- c7f889e: fix(viewport): normalize method names (@caarlos0)
- 39668ec: fix(viewport): normalize method names (#763) (@caarlos0)
- 7ab08fb: fix(viewport): scroll to last line when borders (#706) (@caarlos0)
- 730f5a2: fix: debounce stopwatch and timer (@meowgorithm)
- bd2a5b0: fix: golangci-lint 2 fixes (#769) (@caarlos0)
- 9589cbc: fix: lint issues and disable predeclared linter (#614) (@aymanbagabas)
- 398e92c: fix: remove default character limit (@meowgorithm)
- 1bdd4c6: fix: stopwatch.Start() (#707) (@bevicted)
- 9a262e9: fix: use atomic for ids (#634) (@caarlos0)
Dependency updates
- 1797ac2: feat(deps): bump github.com/charmbracelet/bubbletea from 1.1.0 to 1.1.1 (#611) (@dependabot[bot])
- a9b780a: feat(deps): bump github.com/charmbracelet/lipgloss from 0.13.0 to 0.13.1 (@dependabot[bot])
- db3514c: feat(deps): bump github.com/charmbracelet/lipgloss from 0.13.1 to 1.0.0 (#655) (@dependabot[bot])
- 43aa82c: feat(deps): bump github.com/charmbracelet/x/ansi (#716) (@dependabot[bot])
- c1199d7: feat(deps): bump github.com/charmbracelet/x/ansi from 0.2.3 to 0.3.0 (#613) (@dependabot[bot])
- d3ac47d: feat(deps): bump github.com/charmbracelet/x/ansi from 0.3.0 to 0.3.1 (#615) (@dependabot[bot])
- ed14316: feat(deps): bump github.com/charmbracelet/x/ansi from 0.3.1 to 0.3.2 (#618) (@dependabot[bot])
Documentation updates
- cf3f46a: docs(list): fix grammar in doc comment and README (#627) (@oahshtsua)
- 9e5365e: docs: add example for ValidateFunc (#705) (@bashbunni)
- 87a4e45: docs: additional bubbles (#583) (@caarlos0)
- e3ce11a: docs: update charm & friends blurb (#703) (@bashbunni)
- 178590b: docs: update contributing guidelines (#640) (@bashbunni)
Other work
- 0f9e38c: Don't render suggestions when textinput is not focused. (@elProxy)
- e5296a2: ci: fix goreleaser config (#668) (@caarlos0)
- 76433f7: ci: fix linting on windows (@andreynering)
- d91c9aa: ci: rm .golangci-soft.yml (@caarlos0)
- 2f49443: ci: sync dependabot config (#734) (@charmcli)
- 7a6b190: ci: sync dependabot config (#736) (@charmcli)
- d58217b: ci: sync golangci-lint config (#750) (@github-actions[bot])
- cce8481: ci: sync golangci-lint config (#770) (@github-actions[bot])
- 0305695: ci: test on oldstable and stable go versions, automerge dependabot (#755) (@caarlos0)
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
-
It is disabled by default in v1, but will be enabled in v2. ↩
v2.0.0-beta.1
Ready for Bubbles v2 Beta?
We're excited to share Bubbles v2.0.0-beta.1 with you! This release builds on the last alpha release and includes a few more API changes and improvements. As usual, the upgrade path is pretty straightforward, but let us know if you have any questions.
Bear in mind you’ll want to use this updates alongside v2.0.0-beta.1 versions of Bubble Tea and Lip Gloss.
# Collect them all
go get github.com/charmbracelet/bubbletea@v2.0.0-beta.1
go get github.com/charmbracelet/bubbles@v2.0.0-beta.1
go get github.com/charmbracelet/lipgloss@v2.0.0-beta.1Let’s dive in!
The Init you know and love is back
After spending time with many different forms of Init, we've decided the v1 signature is the right way to go after all. Init is a bit too redundant for our tastes given that initialization is already happening in New. Note that this change is in alignment with current shape of Init in Bubble Tea.
// Before in alpha.2
func (m Model) Init() (Model, tea.Cmd)
// After
func (m Model) Init() tea.CmdWhat’s Changed?
In general, we've made a lot of small changes to Bubbles for consistency and future proofing. There are a lot of changes here, but we've found upgrading pretty easy regardless. If you're having trouble with anything let us know in the discussions.
🥕 List
Styles.FilterCursorandStyles.FilterPrompthave been removed and consolidated into the newStyles.Filter
👟 Progress
- The
Model.EmptyColorandModel.FullColormembers have been changed fromstringtoimage/color.Color Model.Updatenow returns aModelinstead of atea.ModelWithColorProfilehas been removed (Bubble Tea now manages this automatically)WithSolidFillnow takes ancolor.Color(from theimagepackage) instead of astring
✏️ Textarea
The big change to textarea is that you now have the option to use a real cursor per Bubble Tea v2.0.0-beta1. The feature is opt-in, so by default if you don't do anything your programs will continue to use the easy-breezy virtual cursor.
To get an idea of how to use a real cursor with textarea see
the v2 textarea example.
Model.Cursoris now a function that returns a*tea.Cursor(formerly, it was the virtual cursor model)Model.SetCursorhas been renamed toModel.SetCursorColumnCursorStylehas been added to define the cursor styleModel.VirtualCursor(abool) has been added to define whether the textarea should render a virtual cursor; set tofalsewhen using a real cursorStyles.Cursorhas been added to define the cursor style
📜 Textinput
Most of the changes in textinput are to bring it to parity with textarea,
including support for a real cursor. For an example illustrating the use of
textinput with a real cursor see
the v2 textinput example.
Most styling has been moved into StyleState which contains styling for
focused and blurred states.
Model.CompletionStylehas been moved toStylesState.SuggestionModel.PlaceholderStylehas been moved toStylesState.PlaceholderModel.PromptStylehas been and moved toStyleState.PromptModel.TextStylehas been moved toStyleState.TextCursorStylehas been added to define the cursor styleModel.VirtualCursorwas added; use it to disable the virtual cursorModel.Styles(andStyles) was added to house all styling.StyleStatewas added to manage styling for focused and blurred statesModel.Cursoris now afunc() *tea.Cursorfor real cursor support (formerly, it was the virtual cursor model)DefaultStyleshas been addedDefaultDarkStyleshas been addedDefaultLightStyleshas been added
📦 Viewport
The new viewport now supports horizontal scrolling as well as setting a custom gutter column! You can also now scroll horizontally with the left and right arrow keys. Finally, you can now highlight parts of what's being viewed based on regex.
vp := viewport.New()
vp.SetContent("hello world")
// Show line numbers:
vp.LeftGutterFunc = func(info viewport.GutterContext) string {
if info.Soft {
return " │ "
}
if info.Index >= info.TotalLines {
return " ~ │ "
}
return fmt.Sprintf("%4d │ ", info.Index+1)
}
// Highlight things:
vp.SetHighlights(regexp.MustCompile("hello").FindAllStringIndex(vp.GetContent(), -1))
vp.HighlightNext() // highlight and navigate to next match
vp.HighlightPrevious() // highlight and navigate to previous match
vp.ClearHighlights() // clear all highlightsYou may now also let viewport do the soft wrapping for you:
vp := viewport.New()
vp.SoftWrap = true
vp.SetContent("hello world from a very long line")But, if you need more fine control on soft wrapping, you can also use the SetContentLines method. This method allows you to set "virtual lines", which may contain \n in them. These lines are automatically treated as soft wraps, and scrolling also takes then into consideration.
Like details?
Here’s the full changelog since v2.0.0-alpha.2
💝 That’s a wrap!
Feel free to reach out, ask questions, give feedback, and let us know how it's going. We’d love to know what you think.
Part of Charm.
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
v2.0.0-alpha.2
Smells like Bubbles v2 Alpha 2!
Thanks for trying out Bubbles v2.0.0-alpha.2! This release was designed to work with Bubble Tea and Lip Gloss v2 alphas with the same tag, so make sure you catch ’em all:
go get github.com/charmbracelet/bubbletea/v2@v2.0.0-alpha.2
go get github.com/charmbracelet/bubbles/v2@v2.0.0-alpha.2
go get github.com/charmbracelet/lipgloss/v2@v2.0.0-alpha.2There are a lot of small API changes in this release, around two general ideas:
- Consistency across Bubbles
- Manual light/dark background management for Lip Gloss v2 (see below)
We've found upgrading pretty easy, especially with a linter, but let us know how it goes for you. Read on for the breakdown.
Note
When in doubt, check the examples for reference: they've all been updated for v2.
A Note on Light and Dark Styles
Some Bubbles, like help, offer defaults for light and dark background colors. Selecting one or the other now a manual process, and you have two options.
🎩 The Best Way
Ideally, you have Bubble Tea query the background color for you. This means that you'll be properly querying the correct input and outputs with your program, and the query will happen in lockstep with the application.
// Query for the background color.
func (m model) Init() (tea.Model, tea.Cmd) {
return m, tea.RequestBackgroundColor
}
// Listen for the response and initialize your styles accordigly.
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.BackgroundColorMsg:
// Initialize your styles now that you know the background color.
m.help = help.DefaultStyles(msg.IsDark())
return m, nil
}
}If you're using Wish you must do it this way in to get the background color of the client.
🤠 The Quick Way
The quick way is to use detect the background color via the compat package in Lip Gloss. It's less recommended because it contains blocking I/O that operates independently of Bubble Tea and, when used with Wish it will not return the background color of the client (because it's running locally on the server).
import "github.com/charmbracelet/lipgloss/v2/compat"
var hasDarkBG = compat.HasDarkBackground()
func main() {
var m model
h := help.New()
h.Styles = help.DefaultStyles(hasDarkBG)
// And so on...
m.help = h
}For details on the compat package see the Lip Gloss v2.0.0-alpha.2 release notes.
👀 Also Note
You can also just apply defaults manually.
h.Styles = help.DefaultLightStyles() // light mode!
h.Styles = help.DefaultDarkStyles() // jk dark modeWhat’s Changed: the Laundry List
Filepicker
- Removed:
DefaultStylesWithRenderer(). Lip Gloss is pure now, so just useDefaultStyles(). Model.Heighthas been broken into a getter and setter; useModel.SetHeight(int)andModel.Height() intinstead
Help
help now defaults to using colors for dark backgrounds. You can manually change them with DefaultLightStyles() and DefaultDarkStyles():
h := help.New()
h.Styles = help.DefaultDarkStyles() // dark mode
h.Styles = help.DefaultLightStyles() // light modeOr, just detect the background color and apply the appropriate set of styles accordingly:
func (m Model) Init() (tea.Model, tea.Cmd) {
// Ask for the background color.
return m, tea.RequestBackgroundColor
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.BackgroundColorMsg:
// We know the background color, so let’s update our styling.
m.help.Styles = help.DefaultStyles(msg.IsDark())
}
}List
DefaultStyles()now takes a boolean to determine whether it should be rendered with light or dark styles:DefaultStyles(isDark bool)DefaultItemStyles()now takes a boolean to determine whether it should be rendered with light or dark styles:DefaultItemStyles(isDark bool)
Paginator
- The global variable
DefaultKeyMapis now a function:func DefaultKeyMap() KeyMap
Progress
Model.Widthhas been broken into a getter and setter; useModel.SetWidth(int)andModel.Width() intinstead
p := progress.New()
// Before
p.Width = 25
fmt.Printf("%w is a good width", p.Width)
// After
p.SetWidth(25)
fmt.Printf("%w is a good width", p.Width())Stopwatch
NewWithInterval(time.Duration)has been removed. Pass anOptiontoNew()instead:New(WithInterval(time.Duration))
Table
Model.Widthhas been broken into a getter and setter; useModel.SetWidth(int)andModel.Width() intinsteadModel.Heighthas been broken into a getter and setter; useModel.SetHeight(int)andModel.Height() intinstead
Textarea
- The global variable
DefaultKeyMapis now a function:func DefaultKeyMap() KeyMap Model.FocusedStyleandModel.BlurredStylehave been replaced byModel.Styles.FocusedandModel.Styles.BlurredDefaultStyles() (blurred, focused Style)is nowDefaultStyles(isDark bool) Styles. Seehelpabove for an example on how to work with this.
Textinput
- The global variable
DefaultKeyMapis now a function:func DefaultKeyMap() KeyMap Model.Widthhas been broken into a getter and setter; useModel.SetWidth(int)andModel.Width() intinstead
Timer
NewWithInterval(time.Duration)has been removed. Pass anOptiontoNew()instead:New(time.Duration, WithInterval(time.Duration))
Viewport
Model.WidthandModel.Heighthave been replaced with getters and setters:
m := v.New()
// Before
vp.Width = 40
vp.Height = 80
fmt.Println("%d is my favorite width", vp.Width)
// After
vp.SetWidth(40)
vp.SetHeight(80)
fmt.Println("%d is my favorite width", vp.Width())New()doesn’t have deafult args anymore:New(width, height int)is nowNew(...Option). To set an initial width and height do one of the following:
// Use functional arguments:
vp := viewport.New(viewport.WithWidth(40), viewport.WithHeight(80)
// Or just:
vp := viewport.New()
vp.SetWidth(40)
vp.SetHeight(80)
Changelog
- feat(textinput): expose matched suggestions and index by @luevano in #556
- fix: use atomic package for ids by @caarlos0 in #634
- docs(list): fix grammar in doc comment and README by @oahshtsua in #627
- chore: Remove duplicate and redundant code by @cuishuang in #626
- feat(list): implement GlobalIndex helper by @nobe4 in #574
- fix(help): wrong full help sep rendering by @luevano in #554
- chore(help): add small full help test by @meowgorithm in #636
- docs: update contributing guidelines by @bashbunni in #640
- (v2) refactor!: viewport: remove deprecated HighPerformanceRendering by @aymanbagabas in #619
- (v2) refactor!: remove deprecated references by @aymanbagabas in #620
- feat(deps): bump github.com/charmbracelet/lipgloss from 0.13.0 to 0.13.1 by @dependabot in #645
- chore(lint): fix and suppress all soft lint issues; update directives by @meowgorithm in #647
- (v2) tidying up by @meowgorithm in #649
- (v2) chore: minimal updates for Lip Gloss v2 by @meowgorithm in #652
- fix(ci): add lint and lint-sync workflows and update golangci.yml by @aymanbagabas in #651
- (v2) consistency and best practices by @meowgorithm in #656
- feat(deps): bump github.com/charmbracelet/lipgloss from 0.13.1 to 1.0.0 by @dependabot in #655
- Sync golangci-lint config by @github-actions in #660
- (v2) expose function for choosing light or dark styles by @meowgorithm in #663
- fix: debounce stopwatch and timer by @meowgorithm in #664
New Contributors
- @luevano made their first contribution in #556
- @oahshtsua made their first contribution in #627
- @cuishuang made their first contribution in #626
- @nobe4 made their first contribution in #574
- @github-actions made their first contribution in #660
Full Changelog: v2.0.0-alpha.1...v2.0.0-alpha.2
💝 That’s a wrap!
Feel free to reach out, ask questions, give feedback, and let us know how it's going. We’d love to know what you think.
Part of Charm.
v2.0.0-alpha.1
Changelog
New Features
- d019ed3: feat(list): add SetFilterText and SetFilterState (#335) (@taigrr)
- 0fdf5f5: feat: use bubbletea/v2 (@aymanbagabas)
Bug fixes
- 9589cbc: fix: lint issues and disable predeclared linter (#614) (@aymanbagabas)
- f81fd52: fix: spacebar keybinding is now "space" instead of a literal space (@aymanbagabas)
Dependency updates
- 1797ac2: feat(deps): bump github.com/charmbracelet/bubbletea from 1.1.0 to 1.1.1 (#611) (@dependabot[bot])
- c1199d7: feat(deps): bump github.com/charmbracelet/x/ansi from 0.2.3 to 0.3.0 (#613) (@dependabot[bot])
- d3ac47d: feat(deps): bump github.com/charmbracelet/x/ansi from 0.3.0 to 0.3.1 (#615) (@dependabot[bot])
- ed14316: feat(deps): bump github.com/charmbracelet/x/ansi from 0.3.1 to 0.3.2 (#618) (@dependabot[bot])
Other work
- 8972b56: feat!: make Init return the model (@aymanbagabas)
- a93bfef: feat!: use bubbletea@v2-exp (@aymanbagabas)
- a2602f8: feat!: v2: move to v2 module (@aymanbagabas)
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
v0.20.0
Focus. Breathe.
This features support for Bubble Tea's new focus-blur feature as well as a quality-of-life update to paginator. Enjoy!
Focus
You heard that right. Focus-blur window events are now enabled for textinput and textarea which were recently added to Bubble Tea v1.1.0. As long as WithReportFocus is enabled in your Program you'll automatically get nicer inputs.
To enable focus reporting:
p := tea.NewProgram(model{}, tea.WithReportFocus())Remember to stay focused and hydrated!
Paginator opts
Speaking of functional arguments, paginator also received some some new quality-of-life startup options, courtesy @nervo.
p := paginator.New(
paginator.WithPerPage(42),
paginator.WithTotalPages(42),
)Of course, you can still set the values on the model directly too:
p := paginator.New()
p.PerPage = 42
p.TotalPages = 24Happy paging!
Changelog
New!
- d3bd075: feat(cursor): focus/blur support (#581) (@caarlos0)
- 5110925: feat: Introduce paginator options (@nervo)
Deps
- 3eaf8da: feat(deps): bump github.com/charmbracelet/bubbletea from 0.27.0 to 1.0.0 (#604) (@dependabot[bot])
- 6fc27e9: feat(deps): bump github.com/charmbracelet/bubbletea from 1.0.0 to 1.1.0 (#607) (@dependabot[bot])
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.



