从 v1 升级需先阅读 UPGRADE_GUIDE_V2.md,导入路径已改为 charm.land/bubbles/v2;组件需配合 Bubble Tea 使用,不能单独运行;示例代码位于 bubbletea 仓库而非本仓库
bubbles
charmbraceletMIT活跃维护给 Bubble Tea 框架提供现成的终端界面组件库,输入框、列表、表格、进度条等开箱即用,省去自己手写 TUI 控件。
TUI components for Bubble Tea 🫧
go get charm.land/bubbles/v2README 未说明,纯 Go 终端库,无 GPU 或显存要求
Bubble Tea 的官方 TUI 组件库,提供开箱即用的终端界面基础组件
给 Bubble Tea 框架提供现成的终端界面组件库,输入框、列表、表格、进度条等开箱即用,省去自己手写 TUI 控件。
核心能力
- 提供 Spinner、Text Input、Text Area、Table、Progress 等常用组件
- List 组件内置分页、模糊过滤、自动帮助和状态消息
- Viewport 支持垂直滚动、鼠标滚轮和备用屏幕高性能模式
- Key 组件统一管理键位绑定并自动生成帮助视图
- File Picker 支持目录导航和按扩展名过滤文件
容易踩的坑
- v2.2.1最新正式版2026-08-24 · 2026-08-24 09:15 UTC
Tiny Monday bugfix
Textarea element had a bug, where you used to be able to go 1 word backwards, even if there was nothing, which resulted in the whole TUI freezing. It was now fixed by @OxQuasar!
Changelog
Fixed
- f6134114d735cf40e95c15ff62e7359e46d04fc9: fix(textarea): stop word-left at input boundary (#1036) (@OxQuasar)
Docs
- 87d445838781700cb25e3fc3a6b26006eab97b41: docs: update godoc link in readme to new package (@meowgorithm)
Other stuff
- 10489b5fafe92bfaea873297cab8ac8b5c5237f4: readme: drop tree section for now (@meowgorithm)
- 490948109eb4731927ba2f4cd464d8175a9630d7: v2.2.1 (@andrinoff)
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
- v2.2.02026-08-21 · 2026-08-21 08:02 UTC
查看更新说明
A new Bubble and some new Textarea stuff
There are a couple nice features in today's release. Let's check em out!
If a tree falls in the forest…
At last, the tree Bubble from all star contributor @dlvhdr’s is here! He built it for DiffNav and graciously contributed to Bubbles. We've been working with Dolev for a long time and were totally stoked when he offerd to contribute.
Select all, ctrl+c, ctrl+v…done!
Our own @andrinoff added a bunch of awesome stuff to Textarea, including the long awaited text selection. Select, copy, and cut your way into mini text editing.
For more info see the docs.
And special thanks to @zoriya for adding ctrl+left/right bindings.
xoxo, Charm! 💌
Changelog
New!
- 8cea431500a8837c241cbe8ad71601aa8f00a0d9: feat(textarea): support ctrl + left/right/backspace (#1020) (@zoriya)
- 037ad37bb0aa6e8ec544df6d3af752755f0a72d9: feat: textarea selection (#1029) (@andrinoff)
- e97af08ee293131e7b71f7e3006c06a81a2584b8: feat: tree bubble (#893) (@dlvhdr)
Fixed
- 6484e49c0a051fe100cb16c0e184cabd4b53a6f0: fix(filepicker): don't blank the view when height is unset (#1026) (@andrinoff)
Other stuff
- 355f960a27f9b0bf455b3ddea66dc987db62317d: ci: sync dependabot config (#896) (@charmcli)
- ef8af03870c1817d3b0b4fa0578b661a576e0220: v2.2.0 (@andrinoff)
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
- v2.1.12026-07-04 · 2026-07-04 11:43 UTC
查看更新说明
Lil’ Textarea Fix
This is a tiny patch to fix a bug in
textareawhere the prompt character could be missing styling on empty lines. That’s all for now!Thanks for using Bubbles, Charm 🌞
Changelog
Fixed
- d2b2217d6352ce04183623d66d4266115419733c: fix(textarea): apply prompt style on all lines, including fillers (@meowgorithm)
Docs
- a13f110fba4f13ce5fe9bc7fd51619f5782077b9: docs: fix typo in README (#921) (@Rohan5commit)
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.
- v2.1.02026-03-26 · 2026-03-26 19:03 UTC
查看更新说明
Shrink ’n’ grow your textareas
The update adds a new feature to automatically resize your
textareavertically 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!
- f1daacfa0cfee07e31a12498078426d275aa5286: 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.02026-02-24 · 2026-02-24 15:44 UTC
查看更新说明
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 removesAdaptiveColor, 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
compatpackage 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, andtextinput.🔑 The Init You Know and Love is Back
After experimenting with a few different forms of
Initduring the alphas, we decided the v1 signature was the right call after all.Initwas a bit too redundant for our tastes given that initialization already happens inNew:func (m Model) Init() tea.Cmd✨ The Big Highlights
Getters and Setters Everywhere
All components now use getter/setter methods instead of exported
WidthandHeightfields. 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
DefaultKeyMappackage-level variables are now functions, so you get fresh values every time:km := textinput.DefaultKeyMap() // was textinput.D
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Bubbles
Primitives for Bubble Tea applications. These components are used in production in Crush, and many other applications.
[!TIP]
Upgrading from v1? Check out the upgrade guide, or point your LLM at it and let it go to town.
Spinner
A spinner, useful for indicating that some kind an operation is happening. There are a couple default ones, but you can also pass your own ”frames.”
Text Input
A text input field, akin to an <input type="text"> in HTML. Supports unicode,
pasting, in-place scrolling when the value exceeds the width of the element and
the common, and many customization options.
Text Area
A text area field, akin to an <textarea /> in HTML. Allows for input that
spans multiple lines. Supports unicode, pasting, vertical scrolling when the
value exceeds the width and height of the element, and many customization
options.
Table
A component for displaying and navigating tabular data (columns and rows). Supports vertical scrolling and many customization options.
Progress
A simple, customizable progress meter, with optional animation via Harmonica. Supports solid and gradient fills. The empty and filled runes can be set to whatever you'd like. The percentage readout is customizable and can also be omitted entirely.
Paginator
A component for handling pagination logic and optionally drawing pagination UI. Supports "dot-style" pagination (similar to what you might see on iOS) and numeric page numbering, but you could also just use this component for the logic and visualize pagination however you like.
Viewport
A viewport for vertically scrolling content. Optionally includes standard pager keybindings and mouse wheel support. A high performance mode is available for applications which make use of the alternate screen buffer.
This component is well complemented with Reflow for ANSI-aware indenting and text wrapping.
List
A customizable, batteries-included component for browsing a set of items. Features pagination, fuzzy filtering, auto-generated help, an activity spinner, and status messages, all of which can be enabled and disabled as needed. Extrapolated from [Glow][glow].
File Picker
A customizable component for picking a file from the file system. Navigate through directories and select files, optionally limit to certain file extensions.
Timer
A simple, flexible component for counting down. The update frequency and output can be customized as you like.
Stopwatch
A simple, flexible component for counting up. The update frequency and output can be customized as you see fit.
Help
A customizable horizontal mini help view that automatically generates itself from your keybindings. It features single and multi-line modes, which the user can optionally toggle between. It will truncate gracefully if the terminal is too wide for the content.
Key
A non-visual component for managing keybindings. It’s useful for allowing users to remap keybindings as well as generating help views corresponding to your keybindings.
type KeyMap struct {
Up key.Binding
Down key.Binding
}
var DefaultKeyMap = KeyMap{
Up: key.NewBinding(
key.WithKeys("k", "up"), // actual keybindings
key.WithHelp("↑/k", "move up"), // corresponding help text
),
Down: key.NewBinding(
key.WithKeys("j", "down"),
key.WithHelp("↓/j", "move down"),
),
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch {
case key.Matches(msg, DefaultKeyMap.Up):
// The user pressed up
case key.Matches(msg, DefaultKeyMap.Down):
// The user pressed down
}
}
return m, nil
}
There’s more where that came from
To check out community-maintained Bubbles see Charm & Friends. Made a cool Bubble that you want to share? PRs are welcome!
Contributing
See contributing.
Feedback
We’d love to hear your thoughts on this project. Feel free to drop us a note!
License
Part of Charm.
Charm热爱开源 • Charm loves open source