zot themes
zot themes are JSON files that override any subset of the built-in
light/dark TUI theme. Nothing is required: a theme can change one
color, only the spinner, only syntax highlighting, or all of them.
Missing values inherit from zot's built-in default for the detected
terminal background.
Where themes live
User themes are discovered from:
$ZOT_HOME/themes/*.json
Open /settings and choose color theme to switch. Changes are
saved in $ZOT_HOME/config.json and apply immediately. If the selected
file is deleted later, zot resets the setting to the built-in auto
(default) theme.
Theme files bundled with extensions are discovered in-place from loaded
extension directories:
$ZOT_HOME/extensions/<extension>/theme.json
$ZOT_HOME/extensions/<extension>/themes/theme.json
<project>/.zot/extensions/<extension>/theme.json
<project>/.zot/extensions/<extension>/themes/theme.json
zot does not copy extension themes into $ZOT_HOME/themes; extension
owned themes stay in the extension directory. The settings picker shows
source info such as from extension my-theme-extension.
Minimal themes
All of these are valid.
Metadata only:
{
"name": "my-theme",
"description": "Metadata only; all visuals inherit zot defaults."
}
One shared color for both light and dark terminals:
{
"name": "pink-accent",
"colors": {
"accent": 204
}
}
One color per mode:
{
"name": "split-accent",
"colors": {
"dark": { "accent": 204 },
"light": { "accent": 161 }
}
}
Spinner-only:
{
"name": "custom-spinner",
"description": "Only changes the busy spinner.",
"spinner_frames": ["◢", "◣", "◤", "◥"],
"spinner_messages": ["working"],
"spinner_interval_ms": 120
}
Dark-only themes still work on light terminals. If colors.light is
missing, zot applies colors.dark overrides on top of the built-in
light default. The inverse also works.
{
"name": "custom-spinner",
"description": "An alternative spinner for zot that only displays a single spinner text.",
"colors": {
"dark": {
"spinner_frames": ["◢", "◣", "◤", "◥"],
"spinner_messages": ["working"],
"spinner_interval_ms": 120
}
}
}
Full shape
All fields are optional.
{
"name": "my-theme",
"description": "Shown in /settings → color theme.",
"color_descriptions": {
"accent": "Optional documentation for humans. zot ignores this object."
},
"colors": {
"dark": {
"fg": 253,
"muted": 244,
"accent": 111,
"background": "#0b1020",
"user": 180,
"user_bubble_bg": "#42454b",
"user_bubble_fg": 248,
"assistant": 117,
"tool": 114,
"tool_out": 245,
"error": 203,
"warning": 214,
"spinner": 183,
"selection_bg": 24,
"selection_fg": 231,
"spinner_frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"],
"spinner_messages": ["thinking", "working"],
"spinner_interval_ms": 80,
"syntax_base_style": "monokai",
"syntax": {
"keyword": "#81a1c1 bold",
"keyword_constant": "#81a1c1",
"keyword_declaration": "#81a1c1",
"keyword_namespace": "#81a1c1",
"keyword_reserved": "#81a1c1 bold",
"keyword_type": "#88c0d0",
"name_builtin": "#88c0d0",
"name_function": "#8fbcbb",
"name_class": "#a3be8c bold",
"name_decorator": "#b48ead",
"literal_string": "#a3be8c",
"literal_string_escape": "#bf616a",
"literal_number": "#d08770",
"comment": "#616e88 italic",
"comment_preproc": "#b48ead",
"operator": "#eceff4",
"punctuation": "#d8dee9",
"text": "#e5e9f0"
}
},
"light": {
"fg": 236,
"muted": 244,
"accent": 33
}
}
}
You may also put overrides directly at the top level or directly under
colors when they should apply to both modes:
{
"name": "tiny",
"accent": 204,
"colors": {
"spinner_messages": ["shipping"]
}
}
Color fields
Most color fields are xterm-256 indexes (0–255).
fg — default foreground text.
muted — secondary text, dividers, gutters, inactive hints.
accent — prompt bar, bullets, links, headings, active markers.
background — optional full-row TUI background. If missing, zot uses the terminal's existing background. Experimental: terminal background colors can vary by emulator and scrollback behavior; for the most reliable result, change your terminal background color in your terminal settings instead.
user — user role label color; mostly compatibility.
user_bubble_bg — background behind user message rows.
user_bubble_fg — foreground inside user message rows.
assistant — assistant/zot accent and spinner text.
tool — tool names, success marks, diff additions.
tool_out — plain tool-output text.
error — errors, refused calls, diff deletions.
warning — warnings and high context-usage state.
spinner — reserved spinner color slot.
thinkingMax or thinking_max (names retained for compatibility) - status color used when the opt-in max reasoning level is active. Missing values inherit the built-in theme color.
selection_bg — highlighted row background.
selection_fg — highlighted row foreground.
background and user_bubble_bg support richer terminal color forms:
254
"#42454b"
{ "mode": "256", "index": 254 }
{ "mode": "ansi", "index": 100 }
{ "mode": "rgb", "r": 66, "g": 69, "b": 75 }
Spinner fields
Spinner settings can appear at top level, under colors, or under
colors.dark / colors.light.
spinner_frames — list of frame strings. Single-cell glyphs keep
status-bar alignment clean.
spinner_messages — list of messages; zot picks one per turn.
spinner_interval_ms — frame interval in milliseconds. Missing or
invalid falls back to 80ms.
Syntax fields
Syntax highlighting uses Chroma style entries. Values may include
attributes after the color, such as bold, italic, or underline.
Supported syntax override keys:
keyword, keyword_constant, keyword_declaration, keyword_namespace,
keyword_reserved, keyword_type, name_builtin, name_function,
name_class, name_decorator, literal_string, literal_string_escape,
literal_number, comment, comment_preproc, operator, punctuation, text
Example:
{
"colors": {
"dark": {
"syntax_base_style": "monokai",
"syntax": {
"keyword": "#f05b8d",
"name_function": "#b675f1",
"literal_string": "#58c760",
"comment": "#a1a1a1 italic"
}
}
}
}
Theme-only extensions
An extension can exist only to ship a theme. No slash command,
subprocess, or executable is required when the extension contains a
valid theme file.
$ZOT_HOME/extensions/my-theme-extension/
├── extension.json
└── theme.json
extension.json:
{
"name": "my-theme-extension",
"version": "1.0.0",
"description": "Ships a zot color theme",
"enabled": true
}
No exec is needed when theme.json or themes/theme.json exists.
If exec is present, zot treats it as a normal extension too.
Validate
zot theme files are plain JSON, not JSONC. Validate before installing:
python3 -m json.tool theme.json >/dev/null
source
1# zot themes
2
3zot themes are JSON files that override any subset of the built-in
4light/dark TUI theme. Nothing is required: a theme can change one
5color, only the spinner, only syntax highlighting, or all of them.
6Missing values inherit from zot's built-in default for the detected
7terminal background.
8
9## Where themes live
10
11User themes are discovered from:
12
13```text
14$ZOT_HOME/themes/*.json
15```
16
17Open `/settings` and choose **color theme** to switch. Changes are
18saved in `$ZOT_HOME/config.json` and apply immediately. If the selected
19file is deleted later, zot resets the setting to the built-in auto
20(default) theme.
21
22Theme files bundled with extensions are discovered in-place from loaded
23extension directories:
24
25```text
26$ZOT_HOME/extensions/<extension>/theme.json
27$ZOT_HOME/extensions/<extension>/themes/theme.json
28<project>/.zot/extensions/<extension>/theme.json
29<project>/.zot/extensions/<extension>/themes/theme.json
30```
31
32zot does **not** copy extension themes into `$ZOT_HOME/themes`; extension
33owned themes stay in the extension directory. The settings picker shows
34source info such as `from extension my-theme-extension`.
35
36## Minimal themes
37
38All of these are valid.
39
40Metadata only:
41
42```json
43{
44 "name": "my-theme",
45 "description": "Metadata only; all visuals inherit zot defaults."
46}
47```
48
49One shared color for both light and dark terminals:
50
51```json
52{
53 "name": "pink-accent",
54 "colors": {
55 "accent": 204
56 }
57}
58```
59
60One color per mode:
61
62```json
63{
64 "name": "split-accent",
65 "colors": {
66 "dark": { "accent": 204 },
67 "light": { "accent": 161 }
68 }
69}
70```
71
72Spinner-only:
73
74```json
75{
76 "name": "custom-spinner",
77 "description": "Only changes the busy spinner.",
78 "spinner_frames": ["◢", "◣", "◤", "◥"],
79 "spinner_messages": ["working"],
80 "spinner_interval_ms": 120
81}
82```
83
84Dark-only themes still work on light terminals. If `colors.light` is
85missing, zot applies `colors.dark` overrides on top of the built-in
86light default. The inverse also works.
87
88```json
89{
90 "name": "custom-spinner",
91 "description": "An alternative spinner for zot that only displays a single spinner text.",
92 "colors": {
93 "dark": {
94 "spinner_frames": ["◢", "◣", "◤", "◥"],
95 "spinner_messages": ["working"],
96 "spinner_interval_ms": 120
97 }
98 }
99}
100```
101
102## Full shape
103
104All fields are optional.
105
106```json
107{
108 "name": "my-theme",
109 "description": "Shown in /settings → color theme.",
110 "color_descriptions": {
111 "accent": "Optional documentation for humans. zot ignores this object."
112 },
113 "colors": {
114 "dark": {
115 "fg": 253,
116 "muted": 244,
117 "accent": 111,
118 "background": "#0b1020",
119 "user": 180,
120 "user_bubble_bg": "#42454b",
121 "user_bubble_fg": 248,
122 "assistant": 117,
123 "tool": 114,
124 "tool_out": 245,
125 "error": 203,
126 "warning": 214,
127 "spinner": 183,
128 "selection_bg": 24,
129 "selection_fg": 231,
130 "spinner_frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"],
131 "spinner_messages": ["thinking", "working"],
132 "spinner_interval_ms": 80,
133 "syntax_base_style": "monokai",
134 "syntax": {
135 "keyword": "#81a1c1 bold",
136 "keyword_constant": "#81a1c1",
137 "keyword_declaration": "#81a1c1",
138 "keyword_namespace": "#81a1c1",
139 "keyword_reserved": "#81a1c1 bold",
140 "keyword_type": "#88c0d0",
141 "name_builtin": "#88c0d0",
142 "name_function": "#8fbcbb",
143 "name_class": "#a3be8c bold",
144 "name_decorator": "#b48ead",
145 "literal_string": "#a3be8c",
146 "literal_string_escape": "#bf616a",
147 "literal_number": "#d08770",
148 "comment": "#616e88 italic",
149 "comment_preproc": "#b48ead",
150 "operator": "#eceff4",
151 "punctuation": "#d8dee9",
152 "text": "#e5e9f0"
153 }
154 },
155 "light": {
156 "fg": 236,
157 "muted": 244,
158 "accent": 33
159 }
160 }
161}
162```
163
164You may also put overrides directly at the top level or directly under
165`colors` when they should apply to both modes:
166
167```json
168{
169 "name": "tiny",
170 "accent": 204,
171 "colors": {
172 "spinner_messages": ["shipping"]
173 }
174}
175```
176
177## Color fields
178
179Most color fields are xterm-256 indexes (`0`–`255`).
180
181- `fg` — default foreground text.
182- `muted` — secondary text, dividers, gutters, inactive hints.
183- `accent` — prompt bar, bullets, links, headings, active markers.
184- `background` — optional full-row TUI background. If missing, zot uses the terminal's existing background. Experimental: terminal background colors can vary by emulator and scrollback behavior; for the most reliable result, change your terminal background color in your terminal settings instead.
185- `user` — user role label color; mostly compatibility.
186- `user_bubble_bg` — background behind user message rows.
187- `user_bubble_fg` — foreground inside user message rows.
188- `assistant` — assistant/zot accent and spinner text.
189- `tool` — tool names, success marks, diff additions.
190- `tool_out` — plain tool-output text.
191- `error` — errors, refused calls, diff deletions.
192- `warning` — warnings and high context-usage state.
193- `spinner` — reserved spinner color slot.
194- `thinkingMax` or `thinking_max` (names retained for compatibility) - status color used when the opt-in `max` reasoning level is active. Missing values inherit the built-in theme color.
195- `selection_bg` — highlighted row background.
196- `selection_fg` — highlighted row foreground.
197
198`background` and `user_bubble_bg` support richer terminal color forms:
199
200```json
201254
202"#42454b"
203{ "mode": "256", "index": 254 }
204{ "mode": "ansi", "index": 100 }
205{ "mode": "rgb", "r": 66, "g": 69, "b": 75 }
206```
207
208## Spinner fields
209
210Spinner settings can appear at top level, under `colors`, or under
211`colors.dark` / `colors.light`.
212
213- `spinner_frames` — list of frame strings. Single-cell glyphs keep
214 status-bar alignment clean.
215- `spinner_messages` — list of messages; zot picks one per turn.
216- `spinner_interval_ms` — frame interval in milliseconds. Missing or
217 invalid falls back to 80ms.
218
219## Syntax fields
220
221Syntax highlighting uses Chroma style entries. Values may include
222attributes after the color, such as `bold`, `italic`, or `underline`.
223
224Supported syntax override keys:
225
226```text
227keyword, keyword_constant, keyword_declaration, keyword_namespace,
228keyword_reserved, keyword_type, name_builtin, name_function,
229name_class, name_decorator, literal_string, literal_string_escape,
230literal_number, comment, comment_preproc, operator, punctuation, text
231```
232
233Example:
234
235```json
236{
237 "colors": {
238 "dark": {
239 "syntax_base_style": "monokai",
240 "syntax": {
241 "keyword": "#f05b8d",
242 "name_function": "#b675f1",
243 "literal_string": "#58c760",
244 "comment": "#a1a1a1 italic"
245 }
246 }
247 }
248}
249```
250
251## Theme-only extensions
252
253An extension can exist only to ship a theme. No slash command,
254subprocess, or executable is required when the extension contains a
255valid theme file.
256
257```text
258$ZOT_HOME/extensions/my-theme-extension/
259├── extension.json
260└── theme.json
261```
262
263`extension.json`:
264
265```json
266{
267 "name": "my-theme-extension",
268 "version": "1.0.0",
269 "description": "Ships a zot color theme",
270 "enabled": true
271}
272```
273
274No `exec` is needed when `theme.json` or `themes/theme.json` exists.
275If `exec` is present, zot treats it as a normal extension too.
276
277## Validate
278
279zot theme files are plain JSON, not JSONC. Validate before installing:
280
281```bash
282python3 -m json.tool theme.json >/dev/null
283```