Skip to content

Latest commit

Β 

History

History
504 lines (366 loc) Β· 13.1 KB

File metadata and controls

504 lines (366 loc) Β· 13.1 KB

Lip Gloss v2 Upgrade Guide

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.


Table of Contents

  1. Quick Start
  2. Module Path
  3. Color System
  4. Renderer Removal
  5. Printing and Color Downsampling
  6. Background Detection and Adaptive Colors
  7. Whitespace Options
  8. Underline
  9. Style API Changes
  10. Tree Subpackage
  11. Removed APIs
  12. Quick Reference Table

Quick Start

For the fastest possible upgrade, do these two things:

1. Use the compat package for adaptive/complete colors

import "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())
}

2. Use Lip Gloss writers for output

// 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.


Module Path

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

Color System

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 removed

All 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, CompleteAdaptiveColor

These 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.


Renderer Removal

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:

  • Replace lipgloss.DefaultRenderer().NewStyle() with lipgloss.NewStyle().
  • Replace renderer.NewStyle() with lipgloss.NewStyle().
  • Remove any *Renderer fields from your types.
  • Remove calls to SetColorProfile β€” use colorprofile.Detect at the output layer instead.

Printing and Color Downsampling

In v1, color downsampling happened inside Style.Render() via the renderer. In v2, Render() always emits full-fidelity ANSI. Downsampling happens when you print.

Standalone Usage

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())

With Bubble Tea

No changes needed. Bubble Tea v2 handles downsampling internally.


Background Detection and Adaptive Colors

Standalone

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)

With Bubble Tea

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"),
        )),
    }
}

Whitespace Options

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

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).


Style API Changes

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 getters return color.Color

// v1
fg := s.GetForeground() // returns TerminalColor

// v2
fg := s.GetForeground() // returns color.Color

New style methods

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.


Tree Subpackage

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.

Removed APIs

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()

Quick Reference Table

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)

Feedback

Questions, issues, or feedback:


Part of Charm.

The Charm logo

Charmηƒ­ηˆ±εΌ€ζΊ β€’ Charm loves open source β€’ نحنُ Ω†Ψ­Ψ¨ Ψ§Ω„Ω…Ψ΅Ψ§Ψ―Ψ± المفΨͺوحة