Appearance
Color Fundamentals β
ANSI 16, 256-color, truecolor, and the escape sequences that carry them
Terminals speak color through escape sequences. Three generations of color specs coexist: ANSI 16 (1980s), 256-color indexed (1990s), and truecolor (2000s). Each adds capability without breaking the last. Understanding which terminal supports which β and how applications negotiate down when needed β is the foundation of every modern TUI.
The three color generations β
ANSI 16 β
The original color model from the VT100 era. Sixteen named colors β 8 base plus 8 bright variants β accessed by SGR codes 30β37 (fg) and 40β47 (bg), plus 90β97 and 100β107 for the bright variants. These are named slots, not specific RGB values. Your terminal emulator chooses what "red" actually looks like; the same ANSI "red" is crimson in Solarized Dark, peach in Gruvbox, and tomato in default xterm.
\e[31m red text \e[0m # normal red
\e[91m bright red text \e[0m # bright red
\e[1;31m bold+red text \e[0m # bold changes color on some terminalsThe user's color scheme maps the 16 names to actual hex values. This is what makes ANSI 16 portable: you write "red" and the user's theme decides whether that's vivid or muted. It's also what makes it unreliable if your app needs a specific shade β the same ANSI code renders differently everywhere.
256-color indexed β
xterm introduced a 256-color palette in the 1990s: 16 ANSI (as above) + 216 RGB cube entries (6Γ6Γ6 with 51-unit steps) + 24 grayscale steps. Accessed via \e[38;5;<n>m for fg and \e[48;5;<n>m for bg.
\e[38;5;196m vivid red (cube) \e[0m # index 196 = #FF0000
\e[38;5;244m middle gray \e[0m # index 244The RGB cube entries (16β231) map to fixed hex values β theme-independent. This is the cheapest way to get "specific color" without requiring truecolor. Index 16β231 formula: 16 + 36*r + 6*g + b where r,g,b β 0..5. Grayscale 232β255: evenly-spaced grays from near-black to near-white.
Terminals almost universally support 256-color (it's 30+ years old). Whether an application uses it depends on $TERM (*-256color signals support) and COLORTERM.
Truecolor (24-bit) β
The modern standard: full 16.7M colors via \e[38;2;<r>;<g>;<b>m (fg) and \e[48;2;<r>;<g>;<b>m (bg). Each channel is 0β255. No palette, no indirection β the terminal renders exactly the RGB you send.
\e[38;2;255;87;34m #FF5722 \e[0mAlmost every modern terminal supports truecolor (Ghostty, Kitty, iTerm2, WezTerm, Alacritty, Windows Terminal, modern xterm, GNOME Terminal). See 24-bit truecolor for the per-terminal matrix.
SGR vs OSC β two different escape families β
Color delivery uses two escape-sequence families:
- SGR (Select Graphic Rendition) β inline character styling. Embedded in text output. The
\e[...mcodes above are SGR. Applied per-cell as the terminal parses the stream. - OSC (Operating System Command) β out-of-band terminal queries and configuration.
\e]10;?\aasks the terminal "what's your foreground color?"; the terminal replies with\e]10;rgb:abcd/ef12/3456\a. OSC sets the palette, not the content.
Your app emits SGR to color its output. Your terminal emits OSC responses when probed. The color scheme lives in OSC; the colored characters flow as SGR. Confusing these is a common source of bugs β see Terminal Detection.
The SGR attrs that aren't colors β
SGR also carries attrs β bold, italic, underline, inverse, dim, strikethrough. These layer on top of color and are independent of it. Universally supported attrs:
| Code | Attr | Note |
|---|---|---|
1 | bold | Some terminals also brighten the color |
2 | dim | Uneven support β alpha-blend on some, intensity-reduction on others |
3 | italic | Truly italic if the font has an italic variant; slanted otherwise |
4 | underline | Basic single underline |
7 | inverse | Swaps fg + bg |
9 | strikethrough | Newer β check terminal matrix |
22 | normal intensity | Turns off bold/dim |
23 | no italic | |
24 | no underline | |
27 | no inverse |
Modern terminals add curly/dotted/dashed underlines, underline colors, and more β see curly underline.
Portability: writing TUI code that works everywhere β
Applications face a choice for each colored output:
- ANSI 16 always β maximum compatibility, user's theme wins, can't pin specific shades. Old-school portability.
- Truecolor always β modern, exact colors, degrades badly on old terminals (color fallback or mojibake).
- Tier-based rendering β detect at startup, emit the best tier the terminal supports. The right answer for 2020s TUIs.
silvery and modern frameworks use approach 3: design in semantic tokens ($primary, $muted, $error), detect the terminal's tier on startup, and let the framework pick ANSI 16 / 256 / truecolor at render time. See Color Detection for the detection mechanisms.
See also β
- Color Schemes β the 22-slot user-configurable scheme
- Color Detection β
NO_COLOR,COLORTERM, OSC probes - Truecolor compliance β per-terminal test results
- 256-color support β baseline-tier compatibility