Ansi.md

Color Design

First, let me save you man-years of effort with my rules of thumb:

  1. Avoid ANSI 16 and ANSI 256 0-15. Here are different ways to show white on green. This screenshot is directly from my terminal. Because this a rule of thumb I will put the full explanation at the bottom of this page. Which one do you think looks best?
# ansi16 white on ansi16 green (borked)
printf '\e[37;42m 1. hello, world \e[0m\n'
# ansi256 brightwhite on ansi256 green (borked)
printf '\e[38;5;15;48;5;2m 2. hello, world \e[0m\n'
# ansi 256 system white on ansi256 system green (AWESOMENESS)
printf '\e[38;5;255;48;5;40m 3. hello, world \e[0m\n'

  1. ANSI 256 for most apps. Avoid colors 0-15, they lie. When I say WHITE, I mean WHITE. Not off white. WHITE. You could go all the way to ANSI 16M, but as your app grows in popularity you will encounter oddball terminals that don't support it. Either use a library that detects & downsamples, or stick with ANSI 256.

  2. Don't set background color. Don't mess with the user's background color, it's jarring. Since we are giving up control over background, you will need to pick two colors for each text style. If you want "blue" text, you will need to select both a light blue (for dark terminals) and a dark blue (for light terminals).

  3. Invert with abandon. White text on a dark color (or vice versa) is an excellent choice for drawing attention.

Special note on downsampling. It's trivial to write a function that takes an RGB color and finds the closest ANSI 256 16-255 color using distance squared. Be careful - that approach completely fails to take human vision into account. You might want to read this, or at least ask your llm to consider the problem.

Color Converter

Behold, my incredible color converter technology. Type in a hex RGB and I will find the closest ansi 256 color and the closest tailwind color. Depending on my needs I might need one or the other. This tool uses DeltaE in Color.js for calculating perceptual distance.

original color

closest ANSI 256 color

index

closest Tailwind color

Color Sets

Tailwind

I build lots of web apps and always use Tailwind. I love the tailwind color set, which makes it easy to pick colors and adjust the brightness. Wouldn't it be nice to use tailwind colors in your cli app? Look no further, here is Tailwind for popular languages:

// Tailwind colors, see https://ansi.md.
var Tailwind = TailwindColors{
	Slate: Palette{
		C50:  "#f8fafc",
		C100: "#f1f5f9",
		C200: "#e2e8f0",
		C300: "#cad5e2",
		C400: "#90a1b9",
		C500: "#62748e",
		C600: "#45556c",
		C700: "#314158",
		C800: "#1d293d",
		C900: "#0f172b",
		C950: "#020618",
	},
	Gray: Palette{
		C50:  "#f9fafb",
		C100: "#f3f4f6",
		C200: "#e5e7eb",
		C300: "#d1d5dc",
		C400: "#99a1af",
		C500: "#6a7282",
		C600: "#4a5565",
		C700: "#364153",
		C800: "#1e2939",
		C900: "#101828",
		C950: "#030712",
	},
	Zinc: Palette{
		C50:  "#fafafa",
		C100: "#f4f4f5",
		C200: "#e4e4e7",
		C300: "#d4d4d8",
		C400: "#9f9fa9",
		C500: "#71717b",
		C600: "#52525c",
		C700: "#3f3f46",
		C800: "#27272a",
		C900: "#18181b",
		C950: "#09090b",
	},
	Neutral: Palette{
		C50:  "#fafafa",
		C100: "#f5f5f5",
		C200: "#e5e5e5",
		C300: "#d4d4d4",
		C400: "#a1a1a1",
		C500: "#737373",
		C600: "#525252",
		C700: "#404040",
		C800: "#262626",
		C900: "#171717",
		C950: "#0a0a0a",
	},
	Stone: Palette{
		C50:  "#fafaf9",
		C100: "#f5f5f4",
		C200: "#e7e5e4",
		C300: "#d6d3d1",
		C400: "#a6a09b",
		C500: "#79716b",
		C600: "#57534d",
		C700: "#44403b",
		C800: "#292524",
		C900: "#1c1917",
		C950: "#0c0a09",
	},
	Mauve: Palette{
		C50:  "#fafafa",
		C100: "#f3f1f3",
		C200: "#e7e4e7",
		C300: "#d7d0d7",
		C400: "#a89ea9",
		C500: "#79697b",
		C600: "#594c5b",
		C700: "#463947",
		C800: "#2a212c",
		C900: "#1d161e",
		C950: "#0c090c",
	},
	Olive: Palette{
		C50:  "#fbfbf9",
		C100: "#f4f4f0",
		C200: "#e8e8e3",
		C300: "#d8d8d0",
		C400: "#abab9c",
		C500: "#7c7c67",
		C600: "#5b5b4b",
		C700: "#474739",
		C800: "#2b2b22",
		C900: "#1d1d16",
		C950: "#0c0c09",
	},
	Mist: Palette{
		C50:  "#f9fbfb",
		C100: "#f1f3f3",
		C200: "#e3e7e8",
		C300: "#d0d6d8",
		C400: "#9ca8ab",
		C500: "#67787c",
		C600: "#4b585b",
		C700: "#394447",
		C800: "#22292b",
		C900: "#161b1d",
		C950: "#090b0c",
	},
	Taupe: Palette{
		C50:  "#fbfaf9",
		C100: "#f3f1f1",
		C200: "#e8e4e3",
		C300: "#d8d2d0",
		C400: "#aba09c",
		C500: "#7c6d67",
		C600: "#5b4f4b",
		C700: "#473c39",
		C800: "#2b2422",
		C900: "#1d1816",
		C950: "#0c0a09",
	},
	Red: Palette{
		C50:  "#fef2f2",
		C100: "#ffe2e2",
		C200: "#ffc9c9",
		C300: "#ffa2a2",
		C400: "#ff6467",
		C500: "#fb2c36",
		C600: "#e7000b",
		C700: "#c10007",
		C800: "#9f0712",
		C900: "#82181a",
		C950: "#460809",
	},
	Orange: Palette{
		C50:  "#fff7ed",
		C100: "#ffedd4",
		C200: "#ffd6a7",
		C300: "#ffb86a",
		C400: "#ff8904",
		C500: "#ff6900",
		C600: "#f54900",
		C700: "#ca3500",
		C800: "#9f2d00",
		C900: "#7e2a0c",
		C950: "#441306",
	},
	Amber: Palette{
		C50:  "#fffbeb",
		C100: "#fef3c6",
		C200: "#fee685",
		C300: "#ffd230",
		C400: "#ffba00",
		C500: "#fd9a00",
		C600: "#e17100",
		C700: "#bb4d00",
		C800: "#973c00",
		C900: "#7b3306",
		C950: "#461901",
	},
	Yellow: Palette{
		C50:  "#fefce8",
		C100: "#fef9c2",
		C200: "#fff085",
		C300: "#ffdf20",
		C400: "#fcc800",
		C500: "#efb100",
		C600: "#d08700",
		C700: "#a65f00",
		C800: "#894b00",
		C900: "#733e0a",
		C950: "#432004",
	},
	Lime: Palette{
		C50:  "#f7fee7",
		C100: "#ecfcca",
		C200: "#d8f999",
		C300: "#bbf451",
		C400: "#9ae600",
		C500: "#7ccf00",
		C600: "#5ea500",
		C700: "#497d00",
		C800: "#3c6300",
		C900: "#35530e",
		C950: "#192e03",
	},
	Green: Palette{
		C50:  "#f0fdf4",
		C100: "#dcfce7",
		C200: "#b9f8cf",
		C300: "#7bf1a8",
		C400: "#05df72",
		C500: "#00c950",
		C600: "#00a63e",
		C700: "#008236",
		C800: "#016630",
		C900: "#0d542b",
		C950: "#032e15",
	},
	Emerald: Palette{
		C50:  "#ecfdf5",
		C100: "#d0fae5",
		C200: "#a4f4cf",
		C300: "#5ee9b5",
		C400: "#00d492",
		C500: "#00bc7d",
		C600: "#009966",
		C700: "#007a55",
		C800: "#006045",
		C900: "#004f3b",
		C950: "#002c22",
	},
	Teal: Palette{
		C50:  "#f0fdfa",
		C100: "#cbfbf1",
		C200: "#96f7e4",
		C300: "#46ecd5",
		C400: "#00d5be",
		C500: "#00bba7",
		C600: "#009689",
		C700: "#00786f",
		C800: "#005f5a",
		C900: "#0b4f4a",
		C950: "#022f2e",
	},
	Cyan: Palette{
		C50:  "#ecfeff",
		C100: "#cefafe",
		C200: "#a2f4fd",
		C300: "#53eafd",
		C400: "#00d3f2",
		C500: "#00b8db",
		C600: "#0092b8",
		C700: "#007595",
		C800: "#005f78",
		C900: "#104e64",
		C950: "#053345",
	},
	Sky: Palette{
		C50:  "#f0f9ff",
		C100: "#dff2fe",
		C200: "#b8e6fe",
		C300: "#74d4ff",
		C400: "#00bcff",
		C500: "#00a6f4",
		C600: "#0084d1",
		C700: "#0069a8",
		C800: "#00598a",
		C900: "#024a70",
		C950: "#052f4a",
	},
	Blue: Palette{
		C50:  "#eff6ff",
		C100: "#dbeafe",
		C200: "#bedbff",
		C300: "#8ec5ff",
		C400: "#51a2ff",
		C500: "#2b7fff",
		C600: "#155dfc",
		C700: "#1447e6",
		C800: "#193cb8",
		C900: "#1c398e",
		C950: "#162456",
	},
	Indigo: Palette{
		C50:  "#eef2ff",
		C100: "#e0e7ff",
		C200: "#c6d2ff",
		C300: "#a3b3ff",
		C400: "#7c86ff",
		C500: "#615fff",
		C600: "#4f39f6",
		C700: "#432dd7",
		C800: "#372aac",
		C900: "#312c85",
		C950: "#1e1a4d",
	},
	Violet: Palette{
		C50:  "#f5f3ff",
		C100: "#ede9fe",
		C200: "#ddd6ff",
		C300: "#c4b4ff",
		C400: "#a684ff",
		C500: "#8e51ff",
		C600: "#7f22fe",
		C700: "#7008e7",
		C800: "#5d0ec0",
		C900: "#4d179a",
		C950: "#2f0d68",
	},
	Purple: Palette{
		C50:  "#faf5ff",
		C100: "#f3e8ff",
		C200: "#e9d4ff",
		C300: "#dab2ff",
		C400: "#c27aff",
		C500: "#ad46ff",
		C600: "#9810fa",
		C700: "#8200db",
		C800: "#6e11b0",
		C900: "#59168b",
		C950: "#3c0366",
	},
	Fuchsia: Palette{
		C50:  "#fdf4ff",
		C100: "#fae8ff",
		C200: "#f6cfff",
		C300: "#f4a8ff",
		C400: "#ed6aff",
		C500: "#e12afb",
		C600: "#c800de",
		C700: "#a800b7",
		C800: "#8a0194",
		C900: "#721378",
		C950: "#4b004f",
	},
	Pink: Palette{
		C50:  "#fdf2f8",
		C100: "#fce7f3",
		C200: "#fccee8",
		C300: "#fda5d5",
		C400: "#fb64b6",
		C500: "#f6339a",
		C600: "#e60076",
		C700: "#c6005c",
		C800: "#a3004c",
		C900: "#861043",
		C950: "#510424",
	},
	Rose: Palette{
		C50:  "#fff1f2",
		C100: "#ffe4e6",
		C200: "#ffccd3",
		C300: "#ffa1ad",
		C400: "#ff637e",
		C500: "#ff2056",
		C600: "#ec003f",
		C700: "#c70036",
		C800: "#a50036",
		C900: "#8b0836",
		C950: "#4d0218",
	},
}

type TailwindColors struct {
	Slate   Palette
	Gray    Palette
	Zinc    Palette
	Neutral Palette
	Stone   Palette
	Mauve   Palette
	Olive   Palette
	Mist    Palette
	Taupe   Palette
	Red     Palette
	Orange  Palette
	Amber   Palette
	Yellow  Palette
	Lime    Palette
	Green   Palette
	Emerald Palette
	Teal    Palette
	Cyan    Palette
	Sky     Palette
	Blue    Palette
	Indigo  Palette
	Violet  Palette
	Purple  Palette
	Fuchsia Palette
	Pink    Palette
	Rose    Palette
}

type Palette struct {
	C50  string
	C100 string
	C200 string
	C300 string
	C400 string
	C500 string
	C600 string
	C700 string
	C800 string
	C900 string
	C950 string
}

Catppuccin

These days I use Catppuccin Frappe as my terminal theme and I like sneaking these colors into various places. For example, I use One Dark Pro in zed but I like it better with some Catppuccin Green in there for visibility:

Here is Catppuccin for popular languages:

// Catppuccin colors, see https://ansi.md.
var Catppuccin = CatppuccinColors{
	Latte: Palette{
		Rosewater: "#dc8a78",
		Flamingo:  "#dd7878",
		Pink:      "#ea76cb",
		Mauve:     "#8839ef",
		Red:       "#d20f39",
		Maroon:    "#e64553",
		Peach:     "#fe640b",
		Yellow:    "#df8e1d",
		Green:     "#40a02b",
		Teal:      "#179299",
		Sky:       "#04a5e5",
		Sapphire:  "#209fb5",
		Blue:      "#1e66f5",
		Lavender:  "#7287fd",
		Text:      "#4c4f69",
		Subtext1:  "#5c5f77",
		Subtext0:  "#6c6f85",
		Overlay2:  "#7c7f93",
		Overlay1:  "#8c8fa1",
		Overlay0:  "#9ca0b0",
		Surface2:  "#acb0be",
		Surface1:  "#bcc0cc",
		Surface0:  "#ccd0da",
		Base:      "#eff1f5",
		Mantle:    "#e6e9ef",
		Crust:     "#dce0e8",
	},
	Frappe: Palette{
		Rosewater: "#f2d5cf",
		Flamingo:  "#eebebe",
		Pink:      "#f4b8e4",
		Mauve:     "#ca9ee6",
		Red:       "#e78284",
		Maroon:    "#ea999c",
		Peach:     "#ef9f76",
		Yellow:    "#e5c890",
		Green:     "#a6d189",
		Teal:      "#81c8be",
		Sky:       "#99d1db",
		Sapphire:  "#85c1dc",
		Blue:      "#8caaee",
		Lavender:  "#babbf1",
		Text:      "#c6d0f5",
		Subtext1:  "#b5bfe2",
		Subtext0:  "#a5adce",
		Overlay2:  "#949cbb",
		Overlay1:  "#838ba7",
		Overlay0:  "#737994",
		Surface2:  "#626880",
		Surface1:  "#51576d",
		Surface0:  "#414559",
		Base:      "#303446",
		Mantle:    "#292c3c",
		Crust:     "#232634",
	},
	Macchiato: Palette{
		Rosewater: "#f4dbd6",
		Flamingo:  "#f0c6c6",
		Pink:      "#f5bde6",
		Mauve:     "#c6a0f6",
		Red:       "#ed8796",
		Maroon:    "#ee99a0",
		Peach:     "#f5a97f",
		Yellow:    "#eed49f",
		Green:     "#a6da95",
		Teal:      "#8bd5ca",
		Sky:       "#91d7e3",
		Sapphire:  "#7dc4e4",
		Blue:      "#8aadf4",
		Lavender:  "#b7bdf8",
		Text:      "#cad3f5",
		Subtext1:  "#b8c0e0",
		Subtext0:  "#a5adcb",
		Overlay2:  "#939ab7",
		Overlay1:  "#8087a2",
		Overlay0:  "#6e738d",
		Surface2:  "#5b6078",
		Surface1:  "#494d64",
		Surface0:  "#363a4f",
		Base:      "#24273a",
		Mantle:    "#1e2030",
		Crust:     "#181926",
	},
	Mocha: Palette{
		Rosewater: "#f5e0dc",
		Flamingo:  "#f2cdcd",
		Pink:      "#f5c2e7",
		Mauve:     "#cba6f7",
		Red:       "#f38ba8",
		Maroon:    "#eba0ac",
		Peach:     "#fab387",
		Yellow:    "#f9e2af",
		Green:     "#a6e3a1",
		Teal:      "#94e2d5",
		Sky:       "#89dceb",
		Sapphire:  "#74c7ec",
		Blue:      "#89b4fa",
		Lavender:  "#b4befe",
		Text:      "#cdd6f4",
		Subtext1:  "#bac2de",
		Subtext0:  "#a6adc8",
		Overlay2:  "#9399b2",
		Overlay1:  "#7f849c",
		Overlay0:  "#6c7086",
		Surface2:  "#585b70",
		Surface1:  "#45475a",
		Surface0:  "#313244",
		Base:      "#1e1e2e",
		Mantle:    "#181825",
		Crust:     "#11111b",
	},
}

type CatppuccinColors struct {
	Latte     Palette
	Frappe    Palette
	Macchiato Palette
	Mocha     Palette
}

type Palette struct {
	Rosewater string
	Flamingo  string
	Pink      string
	Mauve     string
	Red       string
	Maroon    string
	Peach     string
	Yellow    string
	Green     string
	Teal      string
	Sky       string
	Sapphire  string
	Blue      string
	Lavender  string
	Text      string
	Subtext1  string
	Subtext0  string
	Overlay2  string
	Overlay1  string
	Overlay0  string
	Surface2  string
	Surface1  string
	Surface0  string
	Base      string
	Mantle    string
	Crust     string
}

Bonus: D3 Ordinal Scales

The amazing D3 project has nicely cureated "ordinal color scales" for presenting data. I use these to draw attention to unrelated bits of data, like columns in a spreadsheet, counts in charts, things like that. Here are the D3 ordinal scale color sets for popular languages:

// D3ordinal colors, see https://ansi.md.
var D3Ordinal = map[string][]string{
	"category10": []string{
		"#1f77b4",
		"#ff7f0e",
		"#2ca02c",
		"#d62728",
		"#9467bd",
		"#8c564b",
		"#e377c2",
		"#7f7f7f",
		"#bcbd22",
		"#17becf",
	},
	"accent": []string{
		"#7fc97f",
		"#beaed4",
		"#fdc086",
		"#ffff99",
		"#386cb0",
		"#f0027f",
		"#bf5b17",
		"#666666",
	},
	"dark2": []string{
		"#1b9e77",
		"#d95f02",
		"#7570b3",
		"#e7298a",
		"#66a61e",
		"#e6ab02",
		"#a6761d",
		"#666666",
	},
	"observable10": []string{
		"#4269d0",
		"#efb118",
		"#ff725c",
		"#6cc5b0",
		"#3ca951",
		"#ff8ab7",
		"#a463f2",
		"#97bbf5",
		"#9c6b4e",
		"#9498a0",
	},
	"paired": []string{
		"#a6cee3",
		"#1f78b4",
		"#b2df8a",
		"#33a02c",
		"#fb9a99",
		"#e31a1c",
		"#fdbf6f",
		"#ff7f00",
		"#cab2d6",
		"#6a3d9a",
		"#ffff99",
		"#b15928",
	},
	"pastel1": []string{
		"#fbb4ae",
		"#b3cde3",
		"#ccebc5",
		"#decbe4",
		"#fed9a6",
		"#ffffcc",
		"#e5d8bd",
		"#fddaec",
		"#f2f2f2",
	},
	"pastel2": []string{
		"#b3e2cd",
		"#fdcdac",
		"#cbd5e8",
		"#f4cae4",
		"#e6f5c9",
		"#fff2ae",
		"#f1e2cc",
		"#cccccc",
	},
	"set1": []string{
		"#e41a1c",
		"#377eb8",
		"#4daf4a",
		"#984ea3",
		"#ff7f00",
		"#ffff33",
		"#a65628",
		"#f781bf",
		"#999999",
	},
	"set2": []string{
		"#66c2a5",
		"#fc8d62",
		"#8da0cb",
		"#e78ac3",
		"#a6d854",
		"#ffd92f",
		"#e5c494",
		"#b3b3b3",
	},
	"set3": []string{
		"#8dd3c7",
		"#ffffb3",
		"#bebada",
		"#fb8072",
		"#80b1d3",
		"#fdb462",
		"#b3de69",
		"#fccde5",
		"#d9d9d9",
		"#bc80bd",
		"#ccebc5",
		"#ffed6f",
	},
	"tableau10": []string{
		"#4e79a7",
		"#f28e2c",
		"#e15759",
		"#76b7b2",
		"#59a14f",
		"#edc949",
		"#af7aa1",
		"#ff9da7",
		"#9c755f",
		"#bab0ab",
	},
}

Details: ANSI 16 & ANSI 256 0-15

I find this topic confusing so I'm putting some details in here. All modern terminals let the user customize the "theme", which in effect means customizing ANSI colors 0-16. We are stuck with a primitive system for communicating color between apps and the host terminal. App says "color 4" and the terminal shows the color that the user picked for "blue".

For example, I use the catppuccin/ghostty Ghostty theme, which is setup like this:

# catppuccin-frappe.conf ghostty theme
palette = 0=#51576d
palette = 1=#e78284
palette = 2=#a6d189
palette = 3=#e5c890
palette = 4=#8caaee
palette = 5=#f4b8e4
palette = 6=#81c8be
palette = 7=#a5adce
palette = 8=#626880
palette = 9=#e78284
palette = 10=#a6d189
palette = 11=#e5c890
palette = 12=#8caaee
palette = 13=#f4b8e4
palette = 14=#81c8be
palette = 15=#b5bfe2
...

Eight Normal Colors (Catppuccin Frappe)

name rgb fg16 bg16 fg256 bg256
black #51576d \e[30m \e[40m \e[38;5;0m \e[48;5;0m
red #e78284 \e[31m \e[41m \e[38;5;1m \e[48;5;1m
green #a6d189 \e[32m \e[42m \e[38;5;2m \e[48;5;2m
yellow #e5c890 \e[33m \e[43m \e[38;5;3m \e[48;5;3m
blue #8caaee \e[34m \e[44m \e[38;5;4m \e[48;5;4m
magenta #f4b8e4 \e[35m \e[45m \e[38;5;5m \e[48;5;5m
cyan #81c8be \e[36m \e[46m \e[38;5;6m \e[48;5;6m
white #a5adce \e[37m \e[47m \e[38;5;7m \e[48;5;7m

and of course the anachronistically named "bright" colors are part of the theme:

Eight "Bright" Colors (Catppuccin Frappe)

name rgb fg16 bg16 fg256 bg256
"bright" black #626880 \e[90m \e[100m \e[38;5;8m \e[48;5;8m
"bright" red #e78284 \e[91m \e[101m \e[38;5;9m \e[48;5;9m
"bright" green #a6d189 \e[92m \e[102m \e[38;5;10m \e[48;5;10m
"bright" yellow #e5c890 \e[93m \e[103m \e[38;5;11m \e[48;5;11m
"bright" blue #8caaee \e[94m \e[104m \e[38;5;12m \e[48;5;12m
"bright" magenta #f4b8e4 \e[95m \e[105m \e[38;5;13m \e[48;5;13m
"bright" cyan #81c8be \e[96m \e[106m \e[38;5;14m \e[48;5;14m
"bright" white #b5bfe2 \e[97m \e[107m \e[38;5;15m \e[48;5;15m

And Once Again

To refer back to the example at the top of this page,

# ansi16 white on ansi16 green (borked)
printf '\e[37;42m 1. hello, world \e[0m\n'

# ansi256 brightwhite on ansi256 green (borked)
printf '\e[38;5;15;48;5;2m 2. hello, world \e[0m\n'

# ansi 256 system white on ansi256 system green (AWESOMENESS)
printf '\e[38;5;255;48;5;40m 3. hello, world \e[0m\n'

When we try to create UI with the user's colors, things go astray. That's why I avoid ANSI 16 or ANSI 256 0-15. Just because the internet calls that color "white" doesn't mean it's going to look anything like white. Don't be fooled!