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
| Command | 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 | Key | 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.