Getting Started
Install
go get github.com/floatpane/bubble-overlay
Requires Go 1.26+.
Minimal example
package main
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
overlay "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\nand a fourth")
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))
}
Run it and you'll see the popup painted at row 1, column 4 over the gray inbox view, with the inbox's color preserved on either side of the popup.
Bubble Tea integration
Inside your Model.View():
func (m Model) View() string {
base := m.renderInbox()
if !m.showConfirm {
return base
}
popup := strings.Split(m.renderConfirmPopup(), "\n")
row, col := m.confirmPosition(base, popup)
return overlay.Block(base, popup, row, col)
}
renderInbox and renderConfirmPopup return whatever you want — they
don't need to know about each other. The composite happens at the
view-layer boundary.
Tip
Compute (row, col) once per render from the visible width/height
of the base view. ansi.StringWidth from charmbracelet/x/ansi is
the same width math bubble-overlay uses internally.