Skip to main content

max / alloy_tui

8.6 KB · 144 lines History Blame Raw
1 # Alloy Tokens
2
3 The design-token system for Alloy. Storage format, authoring discipline, extension rules, and how Alloy renders any theme downloaded from the future theme catalog.
4
5 The rules these tokens implement live in [DESIGN-LANGUAGE.md]DESIGN-LANGUAGE.md. This file is where the rules become storage, derivation, and gates.
6
7 ## Storage format: makeover
8
9 Alloy consumes themes in the [makeover]https://git.sr.ht/~maxmj/makeover TOML format, the same schema GoingsOn, Balanced Breakfast, audiofiles, and makenot.work already consume. A theme file has seven sections:
10
11 ```toml
12 [meta]
13 name = "Akari Dawn"
14 variant = "light" # "light" | "dark" | "high-contrast"
15
16 [surface]
17 page = "#e4ded6" # main canvas
18 raised = "#ede7de" # elevated / focused / popup base (lighter than page in light mode)
19 sunken = "#cfc4b6" # recessed panels (darker than page in light mode)
20 overlay = "#f0ece4" # modal / top-most temporary UI
21
22 [content]
23 primary = "#1a1816" # body text
24 secondary = "#222d38" # section headers, labels
25 muted = "#514b45" # disabled, placeholder, metadata
26
27 [action]
28 primary = "#8a4530" # the theme's primary action / brand accent
29
30 [status]
31 danger = "#6a2828" # error
32 success = "#3a5830" # healthy / ok
33 warning = "#b07840" # near-threshold
34 info = "#304050" # informational / link / in-progress
35
36 [line]
37 border = "#cabeae" # single border tone; Alloy derives -subtle and -strong (see below)
38
39 [category]
40 one = "#8a4530" # categorical series colors (chart / syntax / tag rows)
41 two = "#3a5830"
42 three = "#304050"
43 four = "#b07840"
44 five = "#806080"
45 six = "#305858"
46 ```
47
48 Alloy's default light theme is **[Akari Dawn]https://git.sr.ht/~maxmj/makeover/tree/main/item/themes/akari-dawn.toml**; the default dark is **[Akari Night]https://git.sr.ht/~maxmj/makeover/tree/main/item/themes/akari-night.toml**. Both are based on Shu Kutsuzawa's [Akari]https://github.com/cappyzawa/akari-theme (MIT). Users pick a mode at startup (`--theme` flag or `$COLORFGBG` / OSC background query); users can also drop any makeover `.toml` into `~/.config/alloy/themes/` and pass its name to swap.
49
50 **Why makeover as the storage format:** the future user-facing theme catalog (a website of downloadable `.toml` files) is only useful if every make-family app renders any downloaded theme the same way. Sharing the format across apps buys that with zero adapter code.
51
52 ## Alloy-specific derived tokens
53
54 Alloy needs three token tiers makeover ships one of: `border-subtle`, `border`, `border-strong`. Rather than extend the schema (which would break existing consumers), Alloy computes the outer two from `line.border` at load time:
55
56 ```
57 border-subtle = mix(line.border, surface.page, 60%) # pulled toward surface — decorative divider
58 border-strong = mix(line.border, content.primary, 65%) # pulled toward text — focus rings, selection
59 ```
60
61 Mix is in linear sRGB. The 65%-toward-text ratio for `border-strong` is calibrated to reach WCAG AA-UI (≥3.0:1) against `surface.page` even on soft-border themes (Akari's `line.border` is `#cabeae`; the mix produces `#7f786d` which lands at 3.27:1). Themes with crisper borders will overshoot; that's fine.
62
63 The formulas live in `alloy_tui`, not in the theme file. Theme files stay minimal and cross-app compatible.
64
65 ## Authoring discipline: OKLCH mental model, hex on disk
66
67 Themes on disk are hex. The design *reasoning* behind those hex values (Alloy-authored ones or curated ones like Akari) still uses OKLCH:
68
69 - **Ramp discipline.** Surface tiers should follow monotonic L ordering per mode (light: `sunken < page < raised < overlay`; dark: inverted). Accents live in the mid-L / mid-C range where they read as color-carrying-information on either ramp.
70 - **Tinted-not-neutral chrome.** Chrome tones should carry a hue tint (see [DESIGN-LANGUAGE.md]DESIGN-LANGUAGE.md#core-principle-tinted-greyscale-chrome-color-as-information); Akari's warm-clay palette is the canonical worked example.
71 - **Accent-on-glyph, not on body text.** Confirmed by contrast math in the audit: accents that clear WCAG AA-text (4.5:1) on the theme's surface can be used freely; accents that only clear AA-UI (3.0:1) go on glyphs and edge markers, with body text staying at `content.primary`.
72
73 Because storage is hex, OKLCH doesn't need to be preserved in the file; it's the mental model of the person authoring the theme.
74
75 ## Gate: the WCAG audit
76
77 Any theme, downloaded or authored, is audited by [`tools/wcag_audit.py`]../tools/wcag_audit.py. It reads a makeover `.toml`, converts hex to linear sRGB to WCAG 2.1 relative luminance, and reports pass/fail for every affordance-carrying pair (text-on-surface, borders-on-surface, accents-on-surface, elevation deltas). Also computes `border-subtle` / `border-strong` via the same derivation Alloy uses at runtime.
78
79 Run:
80
81 ```
82 python3 tools/wcag_audit.py ../Libraries/makeover/themes/akari-dawn.toml
83 ```
84
85 The audit is not a hard-fail gate: a theme with accents that only reach AA-UI on some surface tiers is still shippable if the accent-on-glyph rule is honored. The audit's job is to make trade-offs visible.
86
87 ## Verified contrast: Akari Dawn
88
89 Full audit output at time of adoption. Method: OKLCH-agnostic; reads hex, computes WCAG 2.1 relative luminance directly.
90
91 **Text on surfaces:**
92
93 | Pair | vs sunken | vs page | vs raised | vs overlay |
94 |---|---:|---:|---:|---:|
95 | `content.primary` | 10.31 | 13.25 | 14.40 | 15.03 |
96 | `content.secondary` | 8.15 | 10.48 | 11.39 | 11.88 |
97 | `content.muted` | 5.01 | 6.44 | 7.00 | 7.30 |
98
99 All twelve pairings clear AA-text (4.5); most clear AAA (7).
100
101 **Borders on surfaces:**
102
103 | Pair | vs page | vs raised | vs overlay |
104 |---|---:|---:|---:|
105 | `border-strong` (derived) | 3.27 | 3.55 | 3.71 |
106 | `line.border` | 1.37 | 1.49 | 1.55 |
107 | `border-subtle` (derived) | 1.12 | 1.22 | 1.27 |
108
109 `border-strong` clears AA-UI on all three surfaces; the derivation formula works for Akari's soft border. `line.border` and `border-subtle` are decorative dividers, not required to meet 3.0.
110
111 **Accents on surfaces:**
112
113 | Accent | vs page | vs raised | vs sunken | vs overlay | Notes |
114 |---|---:|---:|---:|---:|---|
115 | `action.primary` (lantern `#8a4530`) | 5.28 | 5.74 | **4.11** | 5.99 | AA-text on three of four; AA-UI on `sunken`, glyph-safe on the darkest tier |
116 | `status.danger` (`#6a2828`) | 8.07 | 8.78 | 6.28 | 9.15 | AAA everywhere |
117 | `status.success` (`#3a5830`) | 6.00 | 6.53 | 4.67 | 6.81 | AA-text everywhere |
118 | `status.warning` (`#b07840`) | **2.80** | **3.05** | **2.18** | **3.18** | Fails AA-text on all tiers; AA-UI only on `raised`/`overlay`. **Amber-on-cream is inherent to Akari's aesthetic**; per the accent-on-glyph rule, warning is expressed as glyph + edge marker only. |
119 | `status.info` (`#304050`) | 7.96 | 8.65 | 6.19 | 9.03 | AAA / AA-text everywhere |
120
121 The two failure modes are both Akari-inherent, not derivation bugs, and both survive Alloy's discipline: `action.primary` on `surface.sunken` still clears AA-UI (glyph-safe on the darkest recessed panel), and `status.warning`'s amber is a glyph-only accent per the same rule that governs `accent-syntax` in the previous Alloy palette.
122
123 ## Verified contrast: Akari Night
124
125 Text on surfaces all AAA or AA-text. Borders derived at 7+ contrast (dark themes have more headroom because the border tone is against a light content.primary). Notable accent behavior: `status.danger` at `#d25046` lands at 3.3–4.1:1 across surfaces: AA-UI territory, glyph-safe under the same rule. Full audit reproducible via the script.
126
127 ## What's deferred
128
129 - **Calibrated-display verification** on real hardware in real terminals (truecolor and 256-color). Pre-1.0 gate.
130 - **256-color downgrade table** for terminals that report no truecolor.
131 - **Figlet / Nerd-Font recipes** for the large numeric readouts a display font would otherwise carry.
132 - **`alloy theme <name>` subcommand** (see [CONSOLE.md]CONSOLE.md), which wraps theme swap. The console reads themes at startup today, via `--theme` or a guess from `$COLORFGBG`; swapping in place without a relaunch is what remains.
133
134 ## How this file is consumed
135
136 The themes [makeover ships]https://git.sr.ht/~maxmj/makeover/tree/main/item/themes/ are the ground truth. `alloy_tui` at load time:
137
138 1. Reads the selected theme file via the `makeover` crate.
139 2. Computes the two derived tokens (`border-subtle`, `border-strong`) from `line.border`.
140 3. Exposes the full token map as a runtime `Theme` of ratatui `Style` / `Color` values.
141 4. Optionally emits a 256-color fallback table for terminals without truecolor.
142
143 No hex values are hard-coded in Rust. Changing the theme file changes every authored Alloy surface after re-launch (or hot-swap once `alloy theme` ships). Third-party themes downloaded into `~/.config/alloy/themes/` work with zero adapter code.
144