Recipes

Small patterns for common overlay use cases.

Center a popup in the base view

import "github.com/charmbracelet/x/ansi"

func center(base, popup string) string {
    baseLines := strings.Split(base, "\n")
    popupLines := strings.Split(popup, "\n")

    baseW := 0
    for _, l := range baseLines {
        if w := ansi.StringWidth(l); w > baseW {
            baseW = w
        }
    }
    popupW := 0
    for _, l := range popupLines {
        if w := ansi.StringWidth(l); w > popupW {
            popupW = w
        }
    }

    row := (len(baseLines) - len(popupLines)) / 2
    col := (baseW - popupW) / 2
    if row < 0 { row = 0 }
    if col < 0 { col = 0 }
    return overlay.Block(base, popupLines, row, col)
}

Bordered modal

lipgloss borders compose with overlay naturally — render the modal with a Border style, then composite it onto the base:

modal := lipgloss.NewStyle().
    Border(lipgloss.RoundedBorder()).
    BorderForeground(lipgloss.Color("57")).
    Padding(1, 2).
    Render("Delete this email?\n\nEnter — yes   Esc — no")

out := overlay.Block(base, strings.Split(modal, "\n"), 4, 10)

Tooltip on a specific cell

For a single-line tooltip next to a cell at (r, c):

tip := lipgloss.NewStyle().
    Background(lipgloss.Color("236")).
    Foreground(lipgloss.Color("250")).
    Padding(0, 1).
    Render("press ? for help")

out := overlay.Line(rows[r], tip, c+1)
rows[r] = out
view := strings.Join(rows, "\n")

Multiple overlays

Block is just a wrapper around repeated Line calls — you can chain overlays:

view := base
view = overlay.Block(view, statusBar, 0, 0)
view = overlay.Block(view, sidebar, 1, 0)
view = overlay.Block(view, modal,    5, 20)

Later overlays paint over earlier ones, so render in back-to-front order.

Caution

Don't chain overlays inside a tight render loop. Each call rescans the full base. For very large views, render base + overlay region once and cache the result instead.