Skip to content

Commit bf11137

Browse files
committed
docs: add Color Fundamentals / Color Schemes / Color Detection
Three new foundation pages covering terminal color from the ground up: - **Color Fundamentals** (fundamentals/color-fundamentals.md) — ANSI 16 vs 256-color vs truecolor, SGR vs OSC escape families, the universally-supported SGR attrs (bold, dim, italic, underline, inverse, strikethrough), and the tier-based rendering pattern modern TUIs use. - **Color Schemes** (fundamentals/color-schemes.md) — the 22-slot scheme every terminal exposes (16 ANSI + fg/bg/cursor×2/selection×2), how it's queried via OSC 10/11/4/12/17/19, and the 'detect + derive' pattern for adopting the user's theme. - **Color Detection** (fundamentals/color-detection.md) — the full detection stack (NO_COLOR → TERM=dumb → isatty → COLORTERM → $TERM heuristics), OSC probing for authoritative slot values, and graceful tier degradation. All three cross-link each other + to silvery.dev/guide/{color-schemes, capability-tiers,custom-tokens} where applicable. Wired into the VitePress nav + sidebar + transformPageData SEO metadata. Companion pages to silvery.dev/guide/{color-schemes,capability-tiers, custom-tokens} shipped in km-silvery.theme-public-docs — completes the cross-linked ecosystem. Also: frameworks.json silvery entry updated '38 palettes' → '84 color schemes' (catalog growth from the km-silvery.theme-catalog bead). Bead: km-silvery.theme-public-docs (terminfo.dev side)
1 parent eda4e39 commit bf11137

5 files changed

Lines changed: 324 additions & 1 deletion

File tree

content/frameworks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"runtime": "Bun / Node.js",
88
"description": "React TUI framework with 30+ components, layout feedback, 100+ terminal protocol sequences. 177KB gzipped, zero native dependencies.",
99
"baseline": "modern",
10-
"body": "<p><strong>Silvery</strong> is a React-based TUI framework that brings component-driven development to the terminal. With 30+ built-in components (SelectList, TextInput, VirtualList, CommandPalette, ProgressBar), semantic theming with 38 palettes, and layout feedback via useBoxRect, it enables responsive terminal UIs that were previously impossible.</p><p>Silvery works best with the <strong>Modern TUI</strong> baseline — truecolor for its theme system, mouse tracking for interactive components, bracketed paste for text input, and synchronized output for flicker-free rendering. It gracefully degrades on terminals missing Rich features (Kitty keyboard, graphics protocols).</p><div class=\"callout-tip\"><p class=\"callout-title\">Origin story</p><p>Building Silvery required understanding exactly which terminal features could be relied on — and which couldn't. That need led directly to <a href=\"https://termless.dev\" class=\"hover-link\">Termless</a> (headless terminal testing) and <a href=\"/\" class=\"hover-link\">terminfo.dev</a> (this site). The feature matrix, baselines, and probe infrastructure all exist because Silvery needed answers that didn't exist anywhere else.</p></div>"
10+
"body": "<p><strong>Silvery</strong> is a React-based TUI framework that brings component-driven development to the terminal. With 30+ built-in components (SelectList, TextInput, VirtualList, CommandPalette, ProgressBar), semantic theming with 84 color schemes, and layout feedback via useBoxRect, it enables responsive terminal UIs that were previously impossible.</p><p>Silvery works best with the <strong>Modern TUI</strong> baseline — truecolor for its theme system, mouse tracking for interactive components, bracketed paste for text input, and synchronized output for flicker-free rendering. It gracefully degrades on terminals missing Rich features (Kitty keyboard, graphics protocols).</p><div class=\"callout-tip\"><p class=\"callout-title\">Origin story</p><p>Building Silvery required understanding exactly which terminal features could be relied on — and which couldn't. That need led directly to <a href=\"https://termless.dev\" class=\"hover-link\">Termless</a> (headless terminal testing) and <a href=\"/\" class=\"hover-link\">terminfo.dev</a> (this site). The feature matrix, baselines, and probe infrastructure all exist because Silvery needed answers that didn't exist anywhere else.</p></div>"
1111
},
1212
"ink": {
1313
"label": "Ink",

docs/.vitepress/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ function buildSidebar() {
346346
{ text: "TTY Architecture", link: "/fundamentals/tty-architecture" },
347347
{ text: "Terminal Modes & stty", link: "/fundamentals/stty" },
348348
{ text: "Terminal Detection", link: "/fundamentals/term-detection" },
349+
{ text: "Color Fundamentals", link: "/fundamentals/color-fundamentals" },
350+
{ text: "Color Schemes", link: "/fundamentals/color-schemes" },
351+
{ text: "Color Detection", link: "/fundamentals/color-detection" },
349352
{ text: "Terminal Security", link: "/fundamentals/security" },
350353
],
351354
},
@@ -491,6 +494,21 @@ export default defineConfig({
491494
pageData.description =
492495
"Terminal attack surfaces \u2014 OSC 52 clipboard exfiltration, OSC 8 hyperlink spoofing, paste injection without bracketed paste, escape sequence injection in logs, and title bar spoofing."
493496
}
497+
if (rel === "fundamentals/color-fundamentals.md") {
498+
pageData.title = "Terminal Color Fundamentals: ANSI 16, 256-Color, Truecolor"
499+
pageData.description =
500+
"Three generations of terminal color \u2014 ANSI 16 named slots, 256-color indexed cube, 24-bit truecolor hex. SGR vs OSC escape families, the SGR attrs (bold, dim, italic, underline, inverse), and how modern TUIs negotiate tiers."
501+
}
502+
if (rel === "fundamentals/color-schemes.md") {
503+
pageData.title = "Terminal Color Schemes: The 22-Slot User-Configurable Palette"
504+
pageData.description =
505+
"The 22-slot color scheme every major terminal exposes: 16 ANSI + foreground, background, cursor (\u00d7 2), selection (\u00d7 2). How applications adopt the user's theme via OSC 10/11/4/12/17/19 queries."
506+
}
507+
if (rel === "fundamentals/color-detection.md") {
508+
pageData.title = "Terminal Color Detection: NO_COLOR, COLORTERM, OSC Probes"
509+
pageData.description =
510+
"The detection stack TUIs use to pick truecolor, 256-color, ANSI 16, or monochrome \u2014 NO_COLOR, COLORTERM, $TERM, OSC 10/11/4 probing, and graceful tier degradation."
511+
}
494512

495513
// Static index pages
496514
if (rel === "standards.md") {
@@ -690,6 +708,9 @@ export default defineConfig({
690708
{ text: "TTY Architecture", link: "/fundamentals/tty-architecture" },
691709
{ text: "Terminal Modes & stty", link: "/fundamentals/stty" },
692710
{ text: "Terminal Detection", link: "/fundamentals/term-detection" },
711+
{ text: "Color Fundamentals", link: "/fundamentals/color-fundamentals" },
712+
{ text: "Color Schemes", link: "/fundamentals/color-schemes" },
713+
{ text: "Color Detection", link: "/fundamentals/color-detection" },
693714
{ text: "Terminal Security", link: "/fundamentals/security" },
694715
],
695716
},
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
outline: [2, 3]
3+
prev: false
4+
next: false
5+
---
6+
7+
# Color Detection
8+
9+
<p class="page-tagline">NO_COLOR, COLORTERM, OSC probes — how applications figure out what color to emit</p>
10+
11+
<div class="beginner-intro">
12+
<p>Before emitting its first colored byte, a TUI must decide: is this terminal truecolor, 256-color, ANSI 16, or monochrome? Should I use color at all? Applications answer these with a stack of detection signals — environment variables, terminal queries, and explicit opt-outs. Getting the stack right is the difference between "looks great in every terminal" and "spews mojibake on SSH."</p>
13+
</div>
14+
15+
## The detection stack
16+
17+
Modern applications consult signals in this order (highest priority first):
18+
19+
1. **Explicit app flags**`--no-color`, `--color-tier=<tier>`, app-specific `SILVERY_COLOR`, `CLICOLOR_FORCE`
20+
2. **`NO_COLOR`** (any value) → disable color entirely ([no-color.org](https://no-color.org))
21+
3. **`TERM=dumb`** → disable color entirely
22+
4. **Not a TTY** (`!isatty(stdout)`) → disable color (pipe-safe)
23+
5. **`COLORTERM=truecolor` or `=24bit`** → enable truecolor
24+
6. **`TERM=*-direct`** → truecolor (less common convention)
25+
7. **`TERM=*-256color`** → 256-color
26+
8. **`TERM` contains `xterm` / `screen` / `tmux` / `rxvt`** → ANSI 16
27+
9. **Fallback** → ANSI 16 (or mono if in doubt)
28+
29+
This is a heuristic stack. Lower tiers are safer defaults; higher tiers require explicit signals.
30+
31+
## `NO_COLOR` — the universal opt-out
32+
33+
The [no-color.org](https://no-color.org) standard: if `NO_COLOR` is set to any non-empty value, applications MUST NOT add color to their output. This is an accessibility + user-preference feature, not a capability question. Honor it absolutely.
34+
35+
```sh
36+
NO_COLOR=1 myapp # no color
37+
NO_COLOR= myapp # empty → treated as unset (color OK)
38+
```
39+
40+
Implementation: check `process.env.NO_COLOR` before anything else. If set, render mono-only. Applications that attempt to "override" NO_COLOR lose user trust.
41+
42+
## `COLORTERM` — the truecolor flag
43+
44+
Set by terminal emulators that support 24-bit color. Values:
45+
46+
- `truecolor` — canonical
47+
- `24bit` — older alternative
48+
49+
Either value signals truecolor support. Modern terminals set it automatically. If it's unset, assume at most 256-color (the `*-256color` `$TERM` heuristic is a decent backup).
50+
51+
## `$TERM` — the baseline claim
52+
53+
`$TERM` tells you how the terminal wants to be *treated*, not what it actually is. See [Terminal Detection](/fundamentals/term-detection) for the full discussion. For color purposes, the practical rules:
54+
55+
| `$TERM` pattern | Tier inferred |
56+
|-----------------|---------------|
57+
| `dumb` | mono |
58+
| `*-direct` | truecolor |
59+
| `*-256color` | 256-color |
60+
| `*xterm*`, `*screen*`, `*tmux*`, `rxvt*` | ANSI 16 (fallback: assume COLORTERM for truecolor) |
61+
| empty | mono |
62+
63+
Most modern terminals ship `$TERM=xterm-256color` — the broadest compatibility setting — even when they support truecolor. `COLORTERM` is the truecolor signal; `$TERM` is the baseline floor.
64+
65+
## OSC probing — the authoritative check
66+
67+
Environment variables lie. Applications that really need to know can ask the terminal directly via OSC queries:
68+
69+
### OSC 10/11 — foreground/background
70+
71+
```
72+
\e]10;?\a # query foreground
73+
\e]11;?\a # query background
74+
```
75+
76+
Response: `\e]10;rgb:abcd/ef12/3456\a` (each channel is 16-bit hex in most terminals). Timing out? The terminal probably doesn't support the query — degrade gracefully. See [OSC colors](/extensions/osc-colors) for the support matrix.
77+
78+
### OSC 4 — ANSI slots
79+
80+
```
81+
\e]4;<index>;?\a # query ANSI slot index (0–15 portable, 16–255 for 256-color)
82+
```
83+
84+
Same response shape as OSC 10/11. Slot 0–15 are the ANSI palette.
85+
86+
### OSC 12 — cursor color
87+
88+
```
89+
\e]12;?\a # query cursor color (background under cursor)
90+
```
91+
92+
### OSC 17/19 — selection
93+
94+
```
95+
\e]17;?\a # selection background
96+
\e]19;?\a # selection foreground
97+
```
98+
99+
Less widely supported — iTerm2, Kitty, and Terminal.app do; many others drop the query silently.
100+
101+
### Practical probing
102+
103+
- Set a short timeout (100–200ms). Terminals that don't support a query won't respond — don't block on it forever.
104+
- Run queries in parallel when possible (fire all OSC writes, then collect responses).
105+
- Accept partial results: missing slots fall back to formulas (e.g., `cursorText = background` is universally safe).
106+
- Put the terminal into raw mode for the probe; restore the prior mode after.
107+
108+
The [@silvery/theme-detect](https://npmjs.com/package/@silvery/theme-detect) library implements a full probe + fingerprint pipeline you can drop into any TUI.
109+
110+
## Degradation strategy
111+
112+
After detection, emit at the detected tier:
113+
114+
- **truecolor** — full 16.7M via `\e[38;2;r;g;bm`
115+
- **256-color** — quantize hex to the 256-color cube's nearest index, emit `\e[38;5;Nm`
116+
- **ANSI 16** — map to named slots, emit `\e[31m` / `\e[91m` / etc. Let the user's theme decide what "red" looks like.
117+
- **mono** — strip color, rely on SGR attrs (bold, inverse, underline) for hierarchy
118+
119+
Your semantic token (`$error`) resolves to different concrete outputs depending on tier, but the *token* stays the same in your component code. That's the point of the abstraction.
120+
121+
## See also
122+
123+
- [Color Fundamentals](/fundamentals/color-fundamentals) — ANSI 16 / 256 / truecolor escape sequences
124+
- [Color Schemes](/fundamentals/color-schemes) — the 22-slot user-configurable scheme
125+
- [Terminal Detection](/fundamentals/term-detection) — broader detection mechanisms
126+
- [OSC color queries](/extensions/osc-colors) — per-terminal support matrix
127+
- [silvery.dev/guide/capability-tiers](https://silvery.dev/guide/capability-tiers) — silvery's detection + degradation implementation
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
outline: [2, 3]
3+
prev: false
4+
next: false
5+
---
6+
7+
# Color Fundamentals
8+
9+
<p class="page-tagline">ANSI 16, 256-color, truecolor, and the escape sequences that carry them</p>
10+
11+
<div class="beginner-intro">
12+
<p>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.</p>
13+
</div>
14+
15+
## The three color generations
16+
17+
### ANSI 16
18+
19+
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.
20+
21+
```
22+
\e[31m red text \e[0m # normal red
23+
\e[91m bright red text \e[0m # bright red
24+
\e[1;31m bold+red text \e[0m # bold changes color on some terminals
25+
```
26+
27+
The 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.
28+
29+
### 256-color indexed
30+
31+
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.
32+
33+
```
34+
\e[38;5;196m vivid red (cube) \e[0m # index 196 = #FF0000
35+
\e[38;5;244m middle gray \e[0m # index 244
36+
```
37+
38+
The 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.
39+
40+
Terminals almost universally support 256-color (it's 30+ years old). Whether an application *uses* it depends on `$TERM` (`*-256color` signals support) and `COLORTERM`.
41+
42+
### Truecolor (24-bit)
43+
44+
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.
45+
46+
```
47+
\e[38;2;255;87;34m #FF5722 \e[0m
48+
```
49+
50+
Almost every modern terminal supports truecolor (Ghostty, Kitty, iTerm2, WezTerm, Alacritty, Windows Terminal, modern xterm, GNOME Terminal). See [Color compliance](/display/color-truecolor) for the per-terminal matrix.
51+
52+
## SGR vs OSC — two different escape families
53+
54+
Color delivery uses two escape-sequence families:
55+
56+
- **SGR (Select Graphic Rendition)** — inline character styling. Embedded in text output. The `\e[...m` codes above are SGR. Applied per-cell as the terminal parses the stream.
57+
- **OSC (Operating System Command)** — out-of-band terminal queries and configuration. `\e]10;?\a` asks 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.
58+
59+
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](/fundamentals/term-detection).
60+
61+
## The SGR attrs that aren't colors
62+
63+
SGR also carries **attrs** — bold, italic, underline, inverse, dim, strikethrough. These layer *on top of* color and are independent of it. Universally supported attrs:
64+
65+
| Code | Attr | Note |
66+
|------|------|------|
67+
| `1` | bold | Some terminals also brighten the color |
68+
| `2` | dim | Uneven support — alpha-blend on some, intensity-reduction on others |
69+
| `3` | italic | Truly italic if the font has an italic variant; slanted otherwise |
70+
| `4` | underline | Basic single underline |
71+
| `7` | inverse | Swaps fg + bg |
72+
| `9` | strikethrough | Newer — check [terminal matrix](/text) |
73+
| `22` | normal intensity | Turns off bold/dim |
74+
| `23` | no italic | |
75+
| `24` | no underline | |
76+
| `27` | no inverse | |
77+
78+
Modern terminals add curly/dotted/dashed underlines, underline colors, and more — see [underlines](/text/underline-style).
79+
80+
## Portability: writing TUI code that works everywhere
81+
82+
Applications face a choice for each colored output:
83+
84+
1. **ANSI 16 always** — maximum compatibility, user's theme wins, can't pin specific shades. Old-school portability.
85+
2. **Truecolor always** — modern, exact colors, degrades badly on old terminals (color fallback or mojibake).
86+
3. **Tier-based rendering** — detect at startup, emit the best tier the terminal supports. The right answer for 2020s TUIs.
87+
88+
[silvery](https://silvery.dev) 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](/fundamentals/color-detection) for the detection mechanisms.
89+
90+
## See also
91+
92+
- [Color Schemes](/fundamentals/color-schemes) — the 22-slot user-configurable scheme
93+
- [Color Detection](/fundamentals/color-detection)`NO_COLOR`, `COLORTERM`, OSC probes
94+
- [Truecolor compliance](/display/color-truecolor) — per-terminal test results
95+
- [256-color support](/display/color-256) — baseline-tier compatibility

0 commit comments

Comments
 (0)