UPGRADE_GUIDE_V2.md
This guide covers migrating from Lip Gloss v1 (github.com/charmbracelet/lipgloss)
to Lip Gloss v2 (charm.land/lipgloss/v2). It is written for both humans and
LLMs performing automated migrations.
For the fastest possible upgrade, do these two things:
compat package for adaptive/complete colorsimport "charm.land/lipgloss/v2/compat"
// v1
color := lipgloss.AdaptiveColor{Light: "#f1f1f1", Dark: "#cccccc"}
// v2
color := compat.AdaptiveColor{Light: lipgloss.Color("#f1f1f1"), Dark: lipgloss.Color("#cccccc")}
The compat package reads stdin/stdout globally, just like v1. To
customize:
import (
"charm.land/lipgloss/v2/compat"
"github.com/charmbracelet/colorprofile"
)
func init() {
compat.HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
compat.Profile = colorprofile.Detect(os.Stderr, os.Environ())
}
// v1
fmt.Println(s)
// v2
lipgloss.Println(s)
This ensures colors are automatically downsampled. If you're using Bubble Tea v2, this step is unnecessary — Bubble Tea handles it for you.
That's the quick path. Read on for the full migration details.
The import path has changed.
// v1
import "github.com/charmbracelet/lipgloss"
// v2
import "charm.land/lipgloss/v2"
Install:
go get charm.land/lipgloss/v2
All subpackages follow the same pattern:
// v1
import "github.com/charmbracelet/lipgloss/table"
import "github.com/charmbracelet/lipgloss/tree"
import "github.com/charmbracelet/lipgloss/list"
// v2
import "charm.land/lipgloss/v2/table"
import "charm.land/lipgloss/v2/tree"
import "charm.land/lipgloss/v2/list"
Search-and-replace pattern:
github.com/charmbracelet/lipgloss → charm.land/lipgloss/v2
This is the most significant API change.
Color is now a function, not a type// v1 — Color is a string type
var c lipgloss.Color = "21"
var c lipgloss.Color = "#ff00ff"
// v2 — Color is a function returning color.Color
var c color.Color = lipgloss.Color("21")
var c color.Color = lipgloss.Color("#ff00ff")
The return type is image/color.Color (from the standard library).
TerminalColor interface is removedAll methods that accepted lipgloss.TerminalColor now accept
image/color.Color:
// v1
func (s Style) Foreground(c TerminalColor) Style
func (s Style) Background(c TerminalColor) Style
func (s Style) BorderForeground(c ...TerminalColor) Style
// v2
func (s Style) Foreground(c color.Color) Style
func (s Style) Background(c color.Color) Style
func (s Style) BorderForeground(c ...color.Color) Style
Migration: Replace every lipgloss.TerminalColor with color.Color and
add import "image/color".
ANSIColor is now an alias// v1 — custom uint type
type ANSIColor uint
// v2 — alias for ansi.IndexedColor
type ANSIColor = ansi.IndexedColor
v2 also exports named constants for the 16 basic ANSI colors:
lipgloss.Black, lipgloss.Red, lipgloss.Green, lipgloss.Yellow,
lipgloss.Blue, lipgloss.Magenta, lipgloss.Cyan, lipgloss.White,
lipgloss.BrightBlack, lipgloss.BrightRed, lipgloss.BrightGreen,
lipgloss.BrightYellow, lipgloss.BrightBlue, lipgloss.BrightMagenta,
lipgloss.BrightCyan, lipgloss.BrightWhite
AdaptiveColor, CompleteColor, CompleteAdaptiveColorThese types have been moved out of the root package. Use the compat package
for a drop-in replacement, or use the new LightDark and Complete helpers
for explicit control:
// v1
color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
// v2 — using compat (quick path)
color := compat.AdaptiveColor{
Light: lipgloss.Color("#0000ff"),
Dark: lipgloss.Color("#000099"),
}
// v2 — using LightDark (recommended)
hasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
lightDark := lipgloss.LightDark(hasDark)
color := lightDark(lipgloss.Color("#0000ff"), lipgloss.Color("#000099"))
// v1
color := lipgloss.CompleteColor{TrueColor: "#ff00ff", ANSI256: "200", ANSI: "5"}
// v2 — using compat
color := compat.CompleteColor{
TrueColor: lipgloss.Color("#ff00ff"),
ANSI256: lipgloss.Color("200"),
ANSI: lipgloss.Color("5"),
}
// v2 — using Complete (recommended)
profile := colorprofile.Detect(os.Stdout, os.Environ())
complete := lipgloss.Complete(profile)
color := complete(lipgloss.Color("5"), lipgloss.Color("200"), lipgloss.Color("#ff00ff"))
Note that compat.AdaptiveColor and friends take color.Color values for
their fields, not strings.
The Renderer type and all associated functions are removed. In v1, every
Style carried a *Renderer pointer and the package maintained a global
default renderer.
// v1 — these no longer exist
lipgloss.DefaultRenderer()
lipgloss.SetDefaultRenderer(r)
lipgloss.NewRenderer(w, opts...)
lipgloss.ColorProfile()
lipgloss.SetColorProfile(p)
renderer.NewStyle()
In v2, Style is a plain value type. There is no renderer. Color
downsampling is handled at the output layer (see next section).
Migration:
lipgloss.DefaultRenderer().NewStyle() with lipgloss.NewStyle().renderer.NewStyle() with lipgloss.NewStyle().*Renderer fields from your types.SetColorProfile — use colorprofile.Detect at the output
layer instead.In v1, color downsampling happened inside Style.Render() via the renderer. In
v2, Render() always emits full-fidelity ANSI. Downsampling happens when you
print.
Use the Lip Gloss writer functions:
s := someStyle.Render("Hello!")
// Print to stdout with automatic downsampling
lipgloss.Println(s)
// Print to stderr
lipgloss.Fprintln(os.Stderr, s)
// Render to a string (downsampled for stdout's profile)
str := lipgloss.Sprint(s)
The default writer targets stdout. To customize:
lipgloss.Writer = colorprofile.NewWriter(os.Stderr, os.Environ())
No changes needed. Bubble Tea v2 handles downsampling internally.
v1 detected the background color automatically via the global renderer. v2 requires explicit queries:
// v1
hasDark := lipgloss.HasDarkBackground()
// v2 — specify the input and output
hasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
Then use LightDark to pick colors:
lightDark := lipgloss.LightDark(hasDark)
fg := lightDark(lipgloss.Color("#333333"), lipgloss.Color("#f1f1f1"))
s := lipgloss.NewStyle().Foreground(fg)
Request the background color in Init and listen for the response:
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.styles = newStyles(msg.IsDark())
}
// ...
}
func newStyles(bgIsDark bool) styles {
lightDark := lipgloss.LightDark(bgIsDark)
return styles{
title: lipgloss.NewStyle().Foreground(lightDark(
lipgloss.Color("#333333"),
lipgloss.Color("#f1f1f1"),
)),
}
}
The separate foreground/background whitespace options have been replaced by a single style option:
// v1
lipgloss.Place(width, height, hPos, vPos, str,
lipgloss.WithWhitespaceForeground(lipgloss.Color("#333")),
lipgloss.WithWhitespaceBackground(lipgloss.Color("#000")),
)
// v2
lipgloss.Place(width, height, hPos, vPos, str,
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().
Foreground(lipgloss.Color("#333")).
Background(lipgloss.Color("#000")),
),
)
Underline(bool) still works for basic on/off. v2 adds fine-grained control:
// v1
s := lipgloss.NewStyle().Underline(true)
// v2 — still works
s := lipgloss.NewStyle().Underline(true)
// v2 — new: specific styles
s := lipgloss.NewStyle().UnderlineStyle(lipgloss.UnderlineCurly)
// v2 — new: colored underlines
s := lipgloss.NewStyle().
UnderlineStyle(lipgloss.UnderlineSingle).
UnderlineColor(lipgloss.Color("#FF0000"))
Internally, Underline(true) is equivalent to UnderlineStyle(UnderlineSingle)
and Underline(false) is equivalent to UnderlineStyle(UnderlineNone).
NewStyle() is no longer tied to a Renderer// v1
s := lipgloss.NewStyle() // uses global renderer
s := renderer.NewStyle() // uses specific renderer
// v2
s := lipgloss.NewStyle() // pure value, no renderer
color.Color// v1
fg := s.GetForeground() // returns TerminalColor
// v2
fg := s.GetForeground() // returns color.Color
| Method | Description |
|---|---|
UnderlineStyle(Underline) | Set underline style (single, double, curly, etc.) |
UnderlineColor(color.Color) | Set underline color |
PaddingChar(rune) | Set the character used for padding fill |
MarginChar(rune) | Set the character used for margin fill |
Hyperlink(link, params...) | Set a clickable hyperlink |
BorderForegroundBlend(...color.Color) | Apply gradient colors to borders |
BorderForegroundBlendOffset(int) | Set the offset for border gradient |
Each has a corresponding Get*, Unset*, and where applicable Get*
accessor.
The import path changes and there are new styling options:
// v1
import "github.com/charmbracelet/lipgloss/tree"
// v2
import "charm.land/lipgloss/v2/tree"
New methods:
IndenterStyle(lipgloss.Style) — set a static style for tree indentation.IndenterStyleFunc(func(Children, int) lipgloss.Style) — conditionally style
indentation.Width(int) — set tree width for padding.The following types and functions no longer exist in v2. This table shows each removed symbol and its replacement.
| v1 Symbol | v2 Replacement |
|---|---|
type Renderer | Removed entirely |
DefaultRenderer() | Not needed |
SetDefaultRenderer(r) | Not needed |
NewRenderer(w, opts...) | Not needed |
ColorProfile() | colorprofile.Detect(w, env) |
SetColorProfile(p) | Set lipgloss.Writer.Profile |
HasDarkBackground() (no args) | lipgloss.HasDarkBackground(in, out) |
SetHasDarkBackground(b) | Not needed — pass bool to LightDark |
type TerminalColor | image/color.Color |
type Color string | func Color(string) color.Color |
type ANSIColor uint | type ANSIColor = ansi.IndexedColor |
type AdaptiveColor | compat.AdaptiveColor or LightDark |
type CompleteColor | compat.CompleteColor or Complete |
type CompleteAdaptiveColor | compat.CompleteAdaptiveColor |
WithWhitespaceForeground(c) | WithWhitespaceStyle(s) |
WithWhitespaceBackground(c) | WithWhitespaceStyle(s) |
renderer.NewStyle() | lipgloss.NewStyle() |
A side-by-side summary for common patterns:
| Task | v1 | v2 |
|---|---|---|
| Import | "github.com/charmbracelet/lipgloss" | "charm.land/lipgloss/v2" |
| Create style | lipgloss.NewStyle() | lipgloss.NewStyle() |
| Hex color | lipgloss.Color("#ff00ff") | lipgloss.Color("#ff00ff") |
| ANSI color | lipgloss.Color("5") | lipgloss.Color("5") or lipgloss.Magenta |
| Adaptive color | lipgloss.AdaptiveColor{Light: "#fff", Dark: "#000"} | compat.AdaptiveColor{Light: lipgloss.Color("#fff"), Dark: lipgloss.Color("#000")} |
| Set foreground | s.Foreground(lipgloss.Color("5")) | s.Foreground(lipgloss.Color("5")) |
| Print with downsampling | fmt.Println(s.Render("hi")) | lipgloss.Println(s.Render("hi")) |
| Detect dark bg | lipgloss.HasDarkBackground() | lipgloss.HasDarkBackground(os.Stdin, os.Stdout) |
| Light/dark color | lipgloss.AdaptiveColor{...} | lipgloss.LightDark(isDark)(light, dark) |
| Whitespace styling | WithWhitespaceForeground(c) | WithWhitespaceStyle(lipgloss.NewStyle().Foreground(c)) |
| Underline | s.Underline(true) | s.Underline(true) or s.UnderlineStyle(lipgloss.UnderlineCurly) |
Questions, issues, or feedback:
Part of Charm.
<a href="https://charm.land/"></a>
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة