Themes
The TUI is colored by semantic roles, not raw hexes. Every render site resolves a role (accent, text_primary, selection_bg, …) through the active theme, so one switch restyles the whole interface at runtime. A theme is one file, 43 keys, done.
Switching themes
| Commande | What it does |
|---|---|
/theme | Opens the interactive picker: ↑/↓ (or j/k) move the cursor and preview live, the whole UI recolors under the arrow keys. Enter applies and persists, Esc (or q) cancels and reverts to whatever was active when the picker opened. |
/theme list | Lists built-in themes and user presets, then re-scans ~/.opencrabs/themes/. Files that fail validation are listed with a human-readable rejection reason, never silently ignored. |
/theme set <name> | Applies a theme immediately and writes it to your config. Lookup is case-insensitive across built-ins and user presets; an unknown name tells you to run /theme list. |
/theme reset | Returns to the default (crab-dark) and clears the config key. |
To pin a theme without the picker, set it in config.toml:
[tui]
theme = "nord"
The value is applied at boot before the first frame. An empty or unknown name falls back to crab-dark. User theme files are re-scanned on every /theme invocation, so editing a preset file hot-loads it without restarting the TUI.
Built-in themes
There are 39 built-in themes: 8 hand-tuned presets plus a curated pack of 31 converted from popular upstream palettes (see Imported themes). crab-dark is the default and matches the historical OpenCrabs look.
| Preset | Vibe |
|---|---|
crab-dark | The house palette: burnt-orange accent, cool grays, deep ink |
dracula | The classic purple-blue dark theme with orange accent |
alucard | Warm dark, muted contrast (Castlevania’s good-boy Dracula) |
monokai | Sublime’s iconic warm dark with pink/yellow |
catppuccin-mocha | Pastel dark, soft and low-contrast |
catppuccin-latte | The same pastels on light surfaces |
solarized-light | Ethan Schoonlif’s light base |
solarized-dark | The original dark base |
The curated pack adds: aura, ayu, carbonfox, catppuccin-frappe, catppuccin-macchiato, cobalt2, cursor, everforest, flexoki, github, gruvbox, gruvbox-light, kanagawa, material, matrix, mercury, nightowl, nord, one-dark, opencode, orng, osaka-jade, palenight, rosepine, rosepine-dawn, synthwave84, tokyonight, tokyonight-storm, vercel, vesper, zenburn.
Gallery
Swatches show four anchor roles per theme (accent, body text, success, ink background), read from the shipped sources. Regenerate with python3 docs/scripts/gen_theme_gallery.py.
| Theme | Type | accent | text | success | ink | Source |
|---|---|---|---|---|---|---|
| crab-dark | default | #d76414 | #c8c8d2 | #50c878 | #14141e | built-in |
| dracula | hand-built | #ffb86c | #f8f8f2 | #50fa7b | #282a36 | built-in |
| alucard | hand-built | #a34d14 | #1f1f1f | #14710a | #1f1f1f | built-in |
| monokai | hand-built | #fd971f | #f8f8f2 | #a6e22e | #272822 | built-in |
| catppuccin-mocha | hand-built | #fab387 | #cdd6f4 | #a6e3a1 | #1e1e2e | built-in |
| catppuccin-latte | hand-built | #fe640b | #4c4f69 | #40a02b | #4c4f69 | built-in |
| solarized-light | hand-built | #cb4b16 | #657b83 | #859900 | #073642 | built-in |
| solarized-dark | hand-built | #cb4b16 | #839496 | #859900 | #002b36 | built-in |
| aura | curated pack | #a277ff | #edecee | #61ffca | #0f0f0f | opencode, dark, 2026-09-11 |
| ayu | curated pack | #e6b450 | #bfbdb6 | #7fd962 | #0b0e14 | opencode, dark, 2026-09-11 |
| carbonfox | curated pack | #ff7eb6 | #f2f4f8 | #25be6a | #161616 | opencode, dark, 2026-09-11 |
| catppuccin-macchiato | curated pack | #f5bde6 | #cad3f5 | #a6da95 | #24273a | opencode, dark, 2026-09-11 |
| cobalt2 | curated pack | #2affdf | #ffffff | #9eff80 | #193549 | opencode, dark, 2026-09-11 |
| cursor | curated pack | #88c0d0 | #e4e4e4 | #3fa266 | #181818 | opencode, dark, 2026-09-11 |
| everforest | curated pack | #d699b6 | #d3c6aa | #a7c080 | #2d353b | opencode, dark, 2026-09-11 |
| flexoki | curated pack | #8b7ec8 | #cecdc3 | #879a39 | #100f0f | opencode, dark, 2026-09-11 |
| github | curated pack | #39c5cf | #c9d1d9 | #3fb950 | #0d1117 | opencode, dark, 2026-09-11 |
| gruvbox | curated pack | #8ec07c | #ebdbb2 | #b8bb26 | #282828 | opencode, dark, 2026-09-11 |
| kanagawa | curated pack | #d27e99 | #dcd7ba | #98bb6c | #1f1f28 | opencode, dark, 2026-09-11 |
| material | curated pack | #89ddff | #eeffff | #c3e88d | #263238 | opencode, dark, 2026-09-11 |
| matrix | curated pack | #c770ff | #62ff94 | #62ff94 | #0a0e0a | opencode, dark, 2026-09-11 |
| mercury | curated pack | #8da4f5 | #dddde5 | #77c599 | #171721 | opencode, dark, 2026-09-11 |
| nightowl | curated pack | #c792ea | #d6deeb | #c5e478 | #011627 | opencode, dark, 2026-09-11 |
| nord | curated pack | #8fbcbb | #eceff4 | #a3be8c | #2e3440 | opencode, dark, 2026-09-11 |
| one-dark | curated pack | #56b6c2 | #abb2bf | #98c379 | #282c34 | opencode, dark, 2026-09-11 |
| opencode | curated pack | #9d7cd8 | #eeeeee | #7fd88f | #0a0a0a | opencode, dark, 2026-09-11 |
| orng | curated pack | #fff7f1 | #eeeeee | #6ba1e6 | #0a0a0a | opencode, dark, 2026-09-11 |
| osaka-jade | curated pack | #549e6a | #c1c497 | #549e6a | #111c18 | opencode, dark, 2026-09-11 |
| palenight | curated pack | #89ddff | #a6accd | #c3e88d | #292d3e | opencode, dark, 2026-09-11 |
| rosepine | curated pack | #ebbcba | #e0def4 | #31748f | #191724 | opencode, dark, 2026-09-11 |
| synthwave84 | curated pack | #b084eb | #ffffff | #72f1b8 | #262335 | opencode, dark, 2026-09-11 |
| tokyonight | curated pack | #ff966c | #c8d3f5 | #c3e88d | #1a1b26 | opencode, dark, 2026-09-11 |
| vercel | curated pack | #8e4ec6 | #ededed | #46a758 | #000000 | opencode, dark, 2026-09-11 |
| vesper | curated pack | #ffc799 | #ffffff | #99ffe4 | #101010 | opencode, dark, 2026-09-11 |
| zenburn | curated pack | #93e0e3 | #dcdccc | #7f9f7f | #3f3f3f | opencode, dark, 2026-09-11 |
| catppuccin-frappe | curated pack | #e5c890 | #c6d0f5 | #a6d189 | #303446 | alacritty-theme, 2026-09-11 |
| gruvbox-light | curated pack | #b57614 | #3c3836 | #98971a | #fbf1c7 | alacritty-theme, 2026-09-11 |
| rosepine-dawn | curated pack | #d38d2f | #575279 | #286983 | #faf4ed | alacritty-theme, 2026-09-11 |
| tokyonight-storm | curated pack | #ff9e64 | #a9b1d6 | #9ece6a | #24283b | alacritty-theme, 2026-09-11 |
The 43 roles
A user theme is a flat TOML file mapping every role to a #RRGGBB hex. Role names double as the TOML keys. What each one colors:
| Group | Touche | Colors |
|---|---|---|
| Accents | accent | Primary emphasis: prompts, active states, the crab orange |
accent_teal | Primary action / selected accent | |
accent_soft | Passive, informational accent (soft white) | |
| Text | text_primary | Body text |
text_secondary | Secondary text | |
text_muted | Muted labels | |
text_dim | Deepest text tier | |
| Chrome grays | gray | Borders, passive labels, info lines (the workhorse) |
gray_mid | Secondary borders, quiet panel text | |
gray_detail | Detail rows under primary rows (tool list) | |
gray_dim | Inactive borders (unfocused input frame) | |
gray_dark | Deeper inactive chrome | |
gray_base | Darkest neutral foreground (plan-widget borders) | |
gray_light | Bright neutral text | |
gray_soft | Soft label text | |
gray_muted | Muted body text (plan widget) | |
| Positive | success | “ok” states, active pane borders |
analytics_green | Mission-control bars and values | |
green_check | Checked-markdown checkboxes | |
| Negative | error | Primary failures |
error_soft | Error titles | |
error_faded | Error bodies | |
| Warning | warning | Amber input hints |
warning_muted | Muted amber | |
amber_muted | Proposed-skill inbox badges | |
| Teal tiers | teal_vivid | Selection/active in input + tools |
teal_bright | Dialogs | |
teal_muted | Completed/skipped plan tasks | |
teal_calm | Plan progress bars | |
| Blue tiers | blue_slate | Help text, input placeholders |
blue_steel | Informational accents (sessions, files, projects) | |
blue_link | Markdown links | |
blue_sky | Projects | |
blue_soft | Chat accents | |
blue_vivid | Onboarding voice brand accent | |
blue_code | Inline code (markdown) | |
| Surfaces | selection_bg | Selected text in the composer |
surface_panel | Mission-control panel background | |
surface_qr | QR panel background (onboarding) | |
surface_code | Code-block background | |
surface_code_alt | Code-block background, alternate shade | |
| Ink | ink | The main background canvas |
| Misc | purple_soft | Proposed-brain-dedup inbox badges |
Decorative animation cycles (like the rotating project badge colors) are deliberately not roles: they stay preset-agnostic.
Writing your own theme
Drop a file at ~/.opencrabs/themes/<name>.toml. The file stem is the theme name. All 43 keys are required; here is a complete valid theme (nord, from the curated pack):
accent = "#8fbcbb"
accent_teal = "#8fbcbb"
accent_soft = "#eceff4"
text_primary = "#eceff4"
text_secondary = "#8b95a7"
text_muted = "#767b84"
text_dim = "#9ca0a8"
gray = "#434c5e"
gray_mid = "#9ca0a8"
gray_detail = "#91959e"
gray_dim = "#7e838c"
gray_dark = "#6b707a"
gray_base = "#434c5e"
gray_light = "#d0d3d9"
gray_soft = "#afb3ba"
gray_muted = "#8d929a"
success = "#a3be8c"
analytics_green = "#a3be8c"
green_check = "#a3be8c"
error = "#bf616a"
error_soft = "#bf616a"
error_faded = "#6f4853"
warning = "#d08770"
warning_muted = "#9f6e62"
amber_muted = "#87625a"
teal_vivid = "#8fbcbb"
teal_bright = "#8fbcbb"
teal_muted = "#6d8c90"
teal_calm = "#5a7177"
blue_slate = "#698f9e"
blue_steel = "#88c0d0"
blue_link = "#81a1c1"
blue_sky = "#88c0d0"
blue_soft = "#648896"
blue_vivid = "#88c0d0"
blue_code = "#88c0d0"
selection_bg = "#505660"
surface_panel = "#3b4252"
surface_qr = "#343a45"
surface_code = "#434c5e"
surface_code_alt = "#494e59"
ink = "#2e3440"
purple_soft = "#81a1c1"
Validation rules
Validation is loud, never silent. A theme file is rejected, with the reason shown by /theme list, when it:
- misses any of the 43 roles: the error names the missing key;
- carries an unknown key: strict typo guard, there is no partial-schema mode;
- uses a malformed value: only
#RRGGBBorRRGGBB, case-insensitive; - fails the contrast floor (eight reading-critical (foreground, background) pairs must hit 2.5:1 WCAG contrast:
text_primary/text_secondary/accent/success/error/warningonink,blue_codeonsurface_code, andgray_midonsurface_panel. Deliberately dim tiers (text_muted,text_dim, …) are excluded) dim is their job; - collides with a built-in name: user files can never shadow a shipped preset.
Everything else is free for the runtime: each valid theme gets its ANSI-256 fallback tier derived automatically, so terminals without truecolor still render it (nearest index in the 6×6×6 cube, grayscale ramp for neutrals).
Theme enforcement (v0.5.3)
Every widget in the TUI now resolves its colours through the theme engine, the last hardcoded ANSI sites are gone, and a suite-level guard fails the build if a new raw ANSI colour site appears (#1634). Themes can also declare an optional canvas background (the source background carries through catalog conversion), so a theme can restyle the whole terminal canvas, not just the widgets on it.
Imported themes
The 31-theme curated pack was generated by the in-tree converter (tui::theme_catalog) from two upstream formats:
- alacritty-theme TOML (
[colors.primary]background/foreground plus the 16 ANSI colors): thealacritty/alacritty-themecatalog, 176 themes; - opencode theme JSON (
defs+themetoken maps with dark/light variants, hex strings, ANSI integers,defsreferences and"none"), thesst/opencodebuilt-in themes, 33 assets.
Both convert through a fixed 43-role mapping (semantic token first, ANSI color or deterministic bg/fg mix as fallback), and every generated file is pre-certified by the same validator user themes face: a converted file that doesn’t validate never ships. Each pack file carries a # source: provenance header naming the upstream repo, file, and fetch date; the gallery table shows it. Upstream palettes belong to their authors; the pack imports their colors, not their names’ goodwill.
The converter is generation-time tooling: the TUI itself only knows the embedded presets and your drop-in files.