palette

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 9 Imported by: 0

README

palette

import "github.com/gechr/x/palette"

Package palette provides stable, hash-based color selection from ordered color palettes, plus curated palettes tuned for light and dark terminal backgrounds.

The mapping from string to color is deterministic: the same text always resolves to the same color for a given palette, across processes and platforms. This makes it suitable for colorizing entities such as environments, hostnames, or identifiers so they stay visually consistent.

Index

type Assigner

Assigner hands out colors from a Palette so distinct keys receive distinct colors in palette order, repeating only after the palette is exhausted. Assignments are remembered, so a key always resolves to the same color for the Assigner's lifetime. Assign keys in a stable order, such as sorted order, to reproduce the same mapping across runs.

An Assigner is safe for concurrent use.

type Assigner struct {
    // contains filtered or unexported fields
}

func NewAssigner
func NewAssigner(colors ...color.Color) *Assigner

NewAssigner returns an Assigner that draws from the given colors. When no colors are given, it defaults to Auto, selecting a palette that matches the terminal background and true-color support. Pass an explicit palette by spreading it, e.g. NewAssigner(TrueColorDark()...). Spreading an empty palette also triggers the Auto fallback; use Palette.Assigner for a palette that must stay empty, such as the result of Palette.Avoiding.

Example
// An Assigner gives distinct keys distinct colors (no duplicates until the
// palette is exhausted) while keeping each key stable. With no colors it
// defaults to Auto for the current terminal.
a := palette.NewAssigner()

fmt.Println(a.Assign("web") == a.Assign("web"))

Output:

true

func (*Assigner) Assign
func (a *Assigner) Assign(key string) color.Color

Assign returns the next palette color for a new key. The choice is remembered, so a key always resolves to the same color thereafter. Colors repeat in palette order after the palette is exhausted. It returns nil when the palette is empty.

func (*Assigner) Palette
func (a *Assigner) Palette() Palette

Palette returns the underlying palette the Assigner draws from.

type Option

Option configures Auto.

type Option func(*config)

func WithDark
func WithDark(dark bool) Option

WithDark tells Auto whether the terminal background is dark, skipping its background detection entirely. Use it when the background was already detected, for example once at startup.

func WithReserved
func WithReserved(reserved ...color.Color) Option

WithReserved keeps the returned palette perceptually clear of the reserved colors, as if the selected palette were passed to Palette.Avoiding: any palette color that could be mistaken for a reserved one is removed. Use it when entity colors render alongside colors that carry meaning of their own, such as a theme's semantic red or a Semantic set. On the non-true-color path, colors are measured as ANSI-256 renders them.

func WithTrueColor
func WithTrueColor() Option

WithTrueColor forces Auto to return the true-color palette, overriding its automatic terminal-capability detection. The caller is responsible for ensuring the terminal supports true color.

type Palette

Palette is an ordered list of colors from which a stable color is chosen for a given string.

type Palette []color.Color

func Auto
func Auto(opts ...Option) Palette

Auto returns a palette matching the terminal, defaulting to the dark palette when background detection is unavailable. By default it selects the true-color palette when the terminal supports true color and the ANSI-256 palette otherwise; pass WithTrueColor to force the true-color palette.

Background detection queries the terminal, waiting up to 10 milliseconds for a response on the first call; the result is cached for the process. Pass WithDark to skip detection entirely, for example when the background was already detected at startup.

Example
// Auto detects the terminal background and true-color support, then colors
// entities so identical strings share a stable color.
p := palette.Auto()

for _, entity := range []string{"alpha", "beta", "alpha"} {
    _ = p.Color(entity)
}

// Force the true-color palette regardless of detection.
p = palette.Auto(palette.WithTrueColor())
fmt.Println(len(p))

Output:

23

func DefaultDark
func DefaultDark() Palette

DefaultDark returns the default ANSI-256 palette tuned for dark backgrounds, ordered so that colors close together in the list stay perceptually distinct.

func DefaultLight
func DefaultLight() Palette

DefaultLight returns the default palette tuned for light backgrounds: the DefaultDark palette darkened for contrast against a light background.

func TrueColorDark
func TrueColorDark() Palette

TrueColorDark returns the 24-bit palette tuned for dark backgrounds. Every color clears 4.5:1 contrast against #1e1e1e and sits at least 0.08 CIEDE2000 from every other, and colors are ordered so that consecutive entries stay distinct from one another as well. Each call returns a fresh palette the caller owns.

func TrueColorLight
func TrueColorLight() Palette

TrueColorLight returns the 24-bit palette tuned for light backgrounds, ordered like TrueColorDark but measured against #fafafa.

func (Palette) Assigner
func (p Palette) Assigner() *Assigner

Assigner returns an Assigner that draws from exactly this palette, even when it is empty - unlike NewAssigner, which treats no colors as a request for Auto. An empty palette assigns nil to every key.

func (Palette) Avoiding
func (p Palette) Avoiding(reserved ...color.Color) Palette

Avoiding returns a new palette without the colors that sit perceptually close to any reserved color, so entity colors cannot be mistaken for a reserved one - a theme's semantic red, say. Closeness is measured with the CIEDE2000 color-difference formula. The remaining colors keep their original order, so a most-separable-first palette stays that way.

Distances are measured on the colors' own RGBA values, so the guarantee is for opaque colors rendered in true color. Rendering in a reduced color profile quantizes colors and can narrow their distances; Auto accounts for this on its non-true-color path by measuring colors as ANSI-256 renders them. Nil and fully transparent reserved colors are ignored; palette colors that cannot be measured are kept.

The mapping from string to color differs between the filtered and unfiltered palettes, since Palette.Color hashes over the palette length. A reserved set that blankets the palette can leave it empty, in which case Palette.Color returns nil; use Palette.Assigner rather than NewAssigner to keep such a palette empty.

Example
// A theme that renders dangerous entities in its own semantic red can keep
// entity colors perceptually clear of it, so the red stays unmistakable.
themeRed := lipgloss.Color("#f38ba8")
p := palette.TrueColorDark().Avoiding(themeRed)

fmt.Println(len(p))

Output:

20

func (Palette) Color
func (p Palette) Color(text string) color.Color

Color returns a stable color for text, or nil when the palette is empty. The same text always yields the same color for a given palette.

Example
p := palette.DefaultDark()

// The same entity always resolves to the same color.
fmt.Println(p.Color("alpha") == p.Color("alpha"))

Output:

true
Example (Empty)
// An empty palette resolves every input to nil, leaving text uncolored.
fmt.Println(palette.Palette(nil).Color("alpha"))

Output:

<nil>

type Semantic

Semantic holds measured colors for conventional message meanings. Like the entity palettes, every color clears 4.5:1 contrast against its target background, and the colors are mutually distinguishable. Which strings warrant a semantic color is the caller's policy; the set only guarantees the colors themselves are legible and distinct.

To keep entity colors perceptually clear of the set, reserve it: pass the Semantic.Colors slice to WithReserved or Palette.Avoiding.

type Semantic struct {
    Danger  color.Color
    Warning color.Color
    Success color.Color
    Info    color.Color
}

func SemanticDark
func SemanticDark() Semantic

SemanticDark returns the semantic set tuned for dark backgrounds, measured against #1e1e1e like TrueColorDark.

Example
// A measured semantic set: every color clears 4.5:1 against a dark
// background, and reserving it keeps entity colors clear of the set.
sem := palette.SemanticDark()
p := palette.Auto(
    palette.WithTrueColor(),
    palette.WithDark(true),
    palette.WithReserved(sem.Colors()...),
)

fmt.Println(len(p) < len(palette.TrueColorDark()))

Output:

true

func SemanticLight
func SemanticLight() Semantic

SemanticLight returns the semantic set tuned for light backgrounds, measured against #fafafa like TrueColorLight.

func (Semantic) Colors
func (s Semantic) Colors() []color.Color

Colors returns the set as a slice, in Danger, Warning, Success, Info order - convenient for Palette.Avoiding and WithReserved.

Documentation

Overview

Package palette provides stable, hash-based color selection from ordered color palettes, plus curated palettes tuned for light and dark terminal backgrounds.

The mapping from string to color is deterministic: the same text always resolves to the same color for a given palette, across processes and platforms. This makes it suitable for colorizing entities such as environments, hostnames, or identifiers so they stay visually consistent.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Assigner added in v0.5.1

type Assigner struct {
	// contains filtered or unexported fields
}

Assigner hands out colors from a Palette so distinct keys receive distinct colors in palette order, repeating only after the palette is exhausted. Assignments are remembered, so a key always resolves to the same color for the Assigner's lifetime. Assign keys in a stable order, such as sorted order, to reproduce the same mapping across runs.

An Assigner is safe for concurrent use.

func NewAssigner added in v0.5.1

func NewAssigner(colors ...color.Color) *Assigner

NewAssigner returns an Assigner that draws from the given colors. When no colors are given, it defaults to Auto, selecting a palette that matches the terminal background and true-color support. Pass an explicit palette by spreading it, e.g. NewAssigner(TrueColorDark()...). Spreading an empty palette also triggers the Auto fallback; use Palette.Assigner for a palette that must stay empty, such as the result of Palette.Avoiding.

Example
package main

import (
	"fmt"

	"github.com/gechr/x/palette"
)

func main() {
	// An Assigner gives distinct keys distinct colors (no duplicates until the
	// palette is exhausted) while keeping each key stable. With no colors it
	// defaults to Auto for the current terminal.
	a := palette.NewAssigner()

	fmt.Println(a.Assign("web") == a.Assign("web"))
}
Output:
true

func (*Assigner) Assign added in v0.5.1

func (a *Assigner) Assign(key string) color.Color

Assign returns the next palette color for a new key. The choice is remembered, so a key always resolves to the same color thereafter. Colors repeat in palette order after the palette is exhausted. It returns nil when the palette is empty.

func (*Assigner) Palette added in v0.5.1

func (a *Assigner) Palette() Palette

Palette returns the underlying palette the Assigner draws from.

type Option

type Option func(*config)

Option configures Auto.

func WithDark added in v0.5.14

func WithDark(dark bool) Option

WithDark tells Auto whether the terminal background is dark, skipping its background detection entirely. Use it when the background was already detected, for example once at startup.

func WithReserved added in v0.5.14

func WithReserved(reserved ...color.Color) Option

WithReserved keeps the returned palette perceptually clear of the reserved colors, as if the selected palette were passed to Palette.Avoiding: any palette color that could be mistaken for a reserved one is removed. Use it when entity colors render alongside colors that carry meaning of their own, such as a theme's semantic red or a Semantic set. On the non-true-color path, colors are measured as ANSI-256 renders them.

func WithTrueColor

func WithTrueColor() Option

WithTrueColor forces Auto to return the true-color palette, overriding its automatic terminal-capability detection. The caller is responsible for ensuring the terminal supports true color.

type Palette

type Palette []color.Color

Palette is an ordered list of colors from which a stable color is chosen for a given string.

func Auto

func Auto(opts ...Option) Palette

Auto returns a palette matching the terminal, defaulting to the dark palette when background detection is unavailable. By default it selects the true-color palette when the terminal supports true color and the ANSI-256 palette otherwise; pass WithTrueColor to force the true-color palette.

Background detection queries the terminal, waiting up to 10 milliseconds for a response on the first call; the result is cached for the process. Pass WithDark to skip detection entirely, for example when the background was already detected at startup.

Example
package main

import (
	"fmt"

	"github.com/gechr/x/palette"
)

func main() {
	// Auto detects the terminal background and true-color support, then colors
	// entities so identical strings share a stable color.
	p := palette.Auto()

	for _, entity := range []string{"alpha", "beta", "alpha"} {
		_ = p.Color(entity)
	}

	// Force the true-color palette regardless of detection.
	p = palette.Auto(palette.WithTrueColor())
	fmt.Println(len(p))
}
Output:
23

func DefaultDark

func DefaultDark() Palette

DefaultDark returns the default ANSI-256 palette tuned for dark backgrounds, ordered so that colors close together in the list stay perceptually distinct.

func DefaultLight

func DefaultLight() Palette

DefaultLight returns the default palette tuned for light backgrounds: the DefaultDark palette darkened for contrast against a light background.

func TrueColorDark

func TrueColorDark() Palette

TrueColorDark returns the 24-bit palette tuned for dark backgrounds. Every color clears 4.5:1 contrast against #1e1e1e and sits at least 0.08 CIEDE2000 from every other, and colors are ordered so that consecutive entries stay distinct from one another as well. Each call returns a fresh palette the caller owns.

func TrueColorLight

func TrueColorLight() Palette

TrueColorLight returns the 24-bit palette tuned for light backgrounds, ordered like TrueColorDark but measured against #fafafa.

func (Palette) Assigner added in v0.5.14

func (p Palette) Assigner() *Assigner

Assigner returns an Assigner that draws from exactly this palette, even when it is empty - unlike NewAssigner, which treats no colors as a request for Auto. An empty palette assigns nil to every key.

func (Palette) Avoiding added in v0.5.14

func (p Palette) Avoiding(reserved ...color.Color) Palette

Avoiding returns a new palette without the colors that sit perceptually close to any reserved color, so entity colors cannot be mistaken for a reserved one - a theme's semantic red, say. Closeness is measured with the CIEDE2000 color-difference formula. The remaining colors keep their original order, so a most-separable-first palette stays that way.

Distances are measured on the colors' own RGBA values, so the guarantee is for opaque colors rendered in true color. Rendering in a reduced color profile quantizes colors and can narrow their distances; Auto accounts for this on its non-true-color path by measuring colors as ANSI-256 renders them. Nil and fully transparent reserved colors are ignored; palette colors that cannot be measured are kept.

The mapping from string to color differs between the filtered and unfiltered palettes, since Palette.Color hashes over the palette length. A reserved set that blankets the palette can leave it empty, in which case Palette.Color returns nil; use Palette.Assigner rather than NewAssigner to keep such a palette empty.

Example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	"github.com/gechr/x/palette"
)

func main() {
	// A theme that renders dangerous entities in its own semantic red can keep
	// entity colors perceptually clear of it, so the red stays unmistakable.
	themeRed := lipgloss.Color("#f38ba8")
	p := palette.TrueColorDark().Avoiding(themeRed)

	fmt.Println(len(p))
}
Output:
20

func (Palette) Color

func (p Palette) Color(text string) color.Color

Color returns a stable color for text, or nil when the palette is empty. The same text always yields the same color for a given palette.

Example
package main

import (
	"fmt"

	"github.com/gechr/x/palette"
)

func main() {
	p := palette.DefaultDark()

	// The same entity always resolves to the same color.
	fmt.Println(p.Color("alpha") == p.Color("alpha"))
}
Output:
true
Example (Empty)
package main

import (
	"fmt"

	"github.com/gechr/x/palette"
)

func main() {
	// An empty palette resolves every input to nil, leaving text uncolored.
	fmt.Println(palette.Palette(nil).Color("alpha"))
}
Output:
<nil>

type Semantic added in v0.5.14

type Semantic struct {
	Danger  color.Color
	Warning color.Color
	Success color.Color
	Info    color.Color
}

Semantic holds measured colors for conventional message meanings. Like the entity palettes, every color clears 4.5:1 contrast against its target background, and the colors are mutually distinguishable. Which strings warrant a semantic color is the caller's policy; the set only guarantees the colors themselves are legible and distinct.

To keep entity colors perceptually clear of the set, reserve it: pass the Semantic.Colors slice to WithReserved or Palette.Avoiding.

func SemanticDark added in v0.5.14

func SemanticDark() Semantic

SemanticDark returns the semantic set tuned for dark backgrounds, measured against #1e1e1e like TrueColorDark.

Example
package main

import (
	"fmt"

	"github.com/gechr/x/palette"
)

func main() {
	// A measured semantic set: every color clears 4.5:1 against a dark
	// background, and reserving it keeps entity colors clear of the set.
	sem := palette.SemanticDark()
	p := palette.Auto(
		palette.WithTrueColor(),
		palette.WithDark(true),
		palette.WithReserved(sem.Colors()...),
	)

	fmt.Println(len(p) < len(palette.TrueColorDark()))
}
Output:
true

func SemanticLight added in v0.5.14

func SemanticLight() Semantic

SemanticLight returns the semantic set tuned for light backgrounds, measured against #fafafa like TrueColorLight.

func (Semantic) Colors added in v0.5.14

func (s Semantic) Colors() []color.Color

Colors returns the set as a slice, in Danger, Warning, Success, Info order - convenient for Palette.Avoiding and WithReserved.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL