bubble-overlay paints rectangular blocks of styled text on top of an
existing ANSI-styled string at a given (row, col) cell position. It's the
missing primitive for modals, popups, tooltips, filepickers, and floating
panels in Bubble Tea /
lipgloss apps — render your
base view as a single string, render your popup as another, then composite.
Features
- SGR-safe. Existing ANSI styles around the overlaid region are preserved; cells underneath are replaced. The overlay is terminated with
\x1b[0m so its styles don't bleed into the row's tail.
- Cell-accurate. Uses
charmbracelet/x/ansi for width — wide-character / emoji handling matches what the terminal actually renders, not byte counts.
- Auto-grow. Overlays that extend past the bottom of the base string append new lines instead of truncating.
- Auto-pad. Overlays past the right edge of a short row are padded with spaces, so a popup on row 3 column 40 still lands correctly when the base row is only 10 cells wide.
- Tiny. Two functions. No state. Drop-in.
Install
go get github.com/floatpane/bubble-overlay
Requires Go 1.26+.
Usage
package main
import (
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/floatpane/bubble-overlay"
)
func main() {
base := lipgloss.NewStyle().
Foreground(lipgloss.Color("240")).
Render("a quiet inbox view\nwith two lines\nand a third")
popup := lipgloss.NewStyle().
Background(lipgloss.Color("57")).
Foreground(lipgloss.Color("231")).
Padding(0, 1).
Render("are you sure?\nyes / no")
block := strings.Split(popup, "\n")
fmt.Println(overlay.Block(base, block, 1, 4))
}
API
// Paint a multi-line block on top of base at (row, col).
func Block(base string, block []string, row, col int) string
// Paint a single overlay line on top of base at col.
func Line(base, overlay string, col int) string
That's the whole API.
When to use this
You have a Bubble Tea View() returning a styled multi-line string and
you want to render a modal/popup over it without:
- Re-rendering the base view with a "modal-shaped hole" cut out of it.
- Walking the ANSI sequences yourself.
- Truncating styles that span across the modal region.
bubble-overlay does the composite for you. The base view stays a single
string; the modal stays a single string; you call Block and emit the
result.
Documentation
Full API reference: pkg.go.dev/github.com/floatpane/bubble-overlay
Contributing
PRs welcome. See CONTRIBUTING.md.
Security
Report vulnerabilities privately via SECURITY.md.
License
MIT. See LICENSE.