name: write-zot-themes
description: Help the user create, install, or package zot themes, including theme-only extensions.
Writing zot themes
Use this skill when the user asks for help creating, editing,
installing, debugging, or packaging a zot color theme. Read this skill
before generating a theme file or advising on theme extension layout.
What a zot theme is
A zot theme is a JSON file that overrides any subset of zot's built-in
light/dark theme values. Theme files are intentionally permissive:
nothing is required. A file may contain only colors, only spinner
settings, only syntax colors, or only metadata. Missing values always
inherit from the built-in detected light/dark default.
User themes are discovered from:
$ZOT_HOME/themes/*.json
Extension themes are discovered in-place from loaded extension dirs:
$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 stores
an absolute path for extension-owned themes and loads that file directly.
Minimal examples
Valid; inherits all colors/spinner/syntax from zot defaults.
{
"name": "my-theme",
"description": "Metadata only; all visuals inherit zot defaults."
}
Change one color for both light and dark
{
"name": "pink-accent",
"colors": {
"accent": 204
}
}
Change one color per mode
{
"name": "split-accent",
"colors": {
"dark": { "accent": 204 },
"light": { "accent": 161 }
}
}
Spinner-only theme
Top-level spinner overrides are valid and apply to both modes.
{
"name": "custom-spinner",
"description": "Only changes the busy spinner.",
"spinner_frames": ["◢", "◣", "◤", "◥"],
"spinner_messages": ["working"],
"spinner_interval_ms": 120
}
Dark-only theme also works in light terminals
If colors.light is missing, zot applies colors.dark overrides on
top of the built-in light default when running in light mode. The
inverse is also true: if dark is missing but light exists, light
settings are used on dark defaults.
{
"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 theme shape
All fields below 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,
"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
fg — default foreground text.
muted — secondary text, dividers, gutters, inactive hints.
accent — prompt bar, bullets, links, headings, active markers.
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.
selection_bg — highlighted row background.
selection_fg — highlighted row foreground.
Most color fields are xterm-256 indexes (0–255).
user_bubble_bg supports 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. Use single-cell glyphs
when possible so status-bar alignment stays clean.
spinner_messages — list of messages; zot chooses one per turn.
spinner_interval_ms — frame interval in milliseconds; must be
positive. Missing/invalid falls back to 80ms.
Example:
{
"spinner_frames": ["◐", "◓", "◑", "◒"],
"spinner_messages": ["shipping pixels", "warming edge cache"],
"spinner_interval_ms": 120
}
Syntax fields
Syntax highlighting uses Chroma style entries. Values may include
attributes after the color, such as bold, italic, or underline.
Common fields:
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"
}
}
}
}
Installing a user theme
mkdir -p "$ZOT_HOME/themes"
cp my-theme.json "$ZOT_HOME/themes/my-theme.json"
Then open /settings and choose color theme. zot switches theme
immediately and persists the selection in $ZOT_HOME/config.json.
If a selected theme file is deleted, zot resets the setting to the
built-in auto/default theme.
Theme-only extensions
A zot extension can exist only to ship a theme. No slash command,
subprocess, or executable is required when the extension contains a
valid theme file.
Layout:
$ZOT_HOME/extensions/my-theme-extension/
├── extension.json
└── theme.json
or:
$ZOT_HOME/extensions/my-theme-extension/
├── extension.json
└── themes/
└── theme.json
extension.json for a theme-only extension:
{
"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.
In /settings → color theme, extension themes show source info in
the description, e.g. from extension my-theme-extension — ....
Recommendations
- Prefer short descriptions; settings wraps them, but concise is
easier to scan.
- Keep spinner frames single-width when possible.
- For theme packages, use a unique extension name so the source label
is clear.
- For user-editable themes, keep comments out of JSON; JSON comments
are not supported.
- Validate with
python3 -m json.tool theme.json before installing.
source
1---
2name: write-zot-themes
3description: Help the user create, install, or package zot themes, including theme-only extensions.
4---
5
6# Writing zot themes
7
8Use this skill when the user asks for help creating, editing,
9installing, debugging, or packaging a zot color theme. Read this skill
10before generating a theme file or advising on theme extension layout.
11
12## What a zot theme is
13
14A zot theme is a JSON file that overrides any subset of zot's built-in
15light/dark theme values. Theme files are intentionally permissive:
16nothing is required. A file may contain only colors, only spinner
17settings, only syntax colors, or only metadata. Missing values always
18inherit from the built-in detected light/dark default.
19
20User themes are discovered from:
21
22```text
23$ZOT_HOME/themes/*.json
24```
25
26Extension themes are discovered in-place from loaded extension dirs:
27
28```text
29$ZOT_HOME/extensions/<extension>/theme.json
30$ZOT_HOME/extensions/<extension>/themes/theme.json
31<project>/.zot/extensions/<extension>/theme.json
32<project>/.zot/extensions/<extension>/themes/theme.json
33```
34
35zot does **not** copy extension themes into `$ZOT_HOME/themes`; extension
36owned themes stay in the extension directory. The settings picker stores
37an absolute path for extension-owned themes and loads that file directly.
38
39## Minimal examples
40
41### Empty / metadata-only theme
42
43Valid; inherits all colors/spinner/syntax from zot defaults.
44
45```json
46{
47 "name": "my-theme",
48 "description": "Metadata only; all visuals inherit zot defaults."
49}
50```
51
52### Change one color for both light and dark
53
54```json
55{
56 "name": "pink-accent",
57 "colors": {
58 "accent": 204
59 }
60}
61```
62
63### Change one color per mode
64
65```json
66{
67 "name": "split-accent",
68 "colors": {
69 "dark": { "accent": 204 },
70 "light": { "accent": 161 }
71 }
72}
73```
74
75### Spinner-only theme
76
77Top-level spinner overrides are valid and apply to both modes.
78
79```json
80{
81 "name": "custom-spinner",
82 "description": "Only changes the busy spinner.",
83 "spinner_frames": ["◢", "◣", "◤", "◥"],
84 "spinner_messages": ["working"],
85 "spinner_interval_ms": 120
86}
87```
88
89### Dark-only theme also works in light terminals
90
91If `colors.light` is missing, zot applies `colors.dark` overrides on
92top of the built-in light default when running in light mode. The
93inverse is also true: if dark is missing but light exists, light
94settings are used on dark defaults.
95
96```json
97{
98 "name": "custom-spinner",
99 "description": "An alternative spinner for zot that only displays a single spinner text.",
100 "colors": {
101 "dark": {
102 "spinner_frames": ["◢", "◣", "◤", "◥"],
103 "spinner_messages": ["working"],
104 "spinner_interval_ms": 120
105 }
106 }
107}
108```
109
110## Full theme shape
111
112All fields below are optional.
113
114```json
115{
116 "name": "my-theme",
117 "description": "Shown in /settings → color theme.",
118 "color_descriptions": {
119 "accent": "Optional documentation for humans. zot ignores this object."
120 },
121 "colors": {
122 "dark": {
123 "fg": 253,
124 "muted": 244,
125 "accent": 111,
126 "user": 180,
127 "user_bubble_bg": "#42454b",
128 "user_bubble_fg": 248,
129 "assistant": 117,
130 "tool": 114,
131 "tool_out": 245,
132 "error": 203,
133 "warning": 214,
134 "spinner": 183,
135 "selection_bg": 24,
136 "selection_fg": 231,
137 "spinner_frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"],
138 "spinner_messages": ["thinking", "working"],
139 "spinner_interval_ms": 80,
140 "syntax_base_style": "monokai",
141 "syntax": {
142 "keyword": "#81a1c1 bold",
143 "keyword_constant": "#81a1c1",
144 "keyword_declaration": "#81a1c1",
145 "keyword_namespace": "#81a1c1",
146 "keyword_reserved": "#81a1c1 bold",
147 "keyword_type": "#88c0d0",
148 "name_builtin": "#88c0d0",
149 "name_function": "#8fbcbb",
150 "name_class": "#a3be8c bold",
151 "name_decorator": "#b48ead",
152 "literal_string": "#a3be8c",
153 "literal_string_escape": "#bf616a",
154 "literal_number": "#d08770",
155 "comment": "#616e88 italic",
156 "comment_preproc": "#b48ead",
157 "operator": "#eceff4",
158 "punctuation": "#d8dee9",
159 "text": "#e5e9f0"
160 }
161 },
162 "light": {
163 "fg": 236,
164 "muted": 244,
165 "accent": 33
166 }
167 }
168}
169```
170
171You may also put overrides directly at the top level, or directly
172under `colors`, when they should apply to both modes:
173
174```json
175{
176 "name": "tiny",
177 "accent": 204,
178 "colors": {
179 "spinner_messages": ["shipping"]
180 }
181}
182```
183
184## Color fields
185
186- `fg` — default foreground text.
187- `muted` — secondary text, dividers, gutters, inactive hints.
188- `accent` — prompt bar, bullets, links, headings, active markers.
189- `user` — user role label color; mostly compatibility.
190- `user_bubble_bg` — background behind user message rows.
191- `user_bubble_fg` — foreground inside user message rows.
192- `assistant` — assistant/zot accent and spinner text.
193- `tool` — tool names, success marks, diff additions.
194- `tool_out` — plain tool-output text.
195- `error` — errors, refused calls, diff deletions.
196- `warning` — warnings and high context-usage state.
197- `spinner` — reserved spinner color slot.
198- `selection_bg` — highlighted row background.
199- `selection_fg` — highlighted row foreground.
200
201Most color fields are xterm-256 indexes (`0`–`255`).
202
203`user_bubble_bg` supports richer terminal color forms:
204
205```json
206254
207"#42454b"
208{ "mode": "256", "index": 254 }
209{ "mode": "ansi", "index": 100 }
210{ "mode": "rgb", "r": 66, "g": 69, "b": 75 }
211```
212
213## Spinner fields
214
215Spinner settings can appear at top level, under `colors`, or under
216`colors.dark` / `colors.light`.
217
218- `spinner_frames` — list of frame strings. Use single-cell glyphs
219 when possible so status-bar alignment stays clean.
220- `spinner_messages` — list of messages; zot chooses one per turn.
221- `spinner_interval_ms` — frame interval in milliseconds; must be
222 positive. Missing/invalid falls back to 80ms.
223
224Example:
225
226```json
227{
228 "spinner_frames": ["◐", "◓", "◑", "◒"],
229 "spinner_messages": ["shipping pixels", "warming edge cache"],
230 "spinner_interval_ms": 120
231}
232```
233
234## Syntax fields
235
236Syntax highlighting uses Chroma style entries. Values may include
237attributes after the color, such as `bold`, `italic`, or `underline`.
238
239Common fields:
240
241- `keyword`
242- `keyword_constant`
243- `keyword_declaration`
244- `keyword_namespace`
245- `keyword_reserved`
246- `keyword_type`
247- `name_builtin`
248- `name_function`
249- `name_class`
250- `name_decorator`
251- `literal_string`
252- `literal_string_escape`
253- `literal_number`
254- `comment`
255- `comment_preproc`
256- `operator`
257- `punctuation`
258- `text`
259
260Example:
261
262```json
263{
264 "colors": {
265 "dark": {
266 "syntax_base_style": "monokai",
267 "syntax": {
268 "keyword": "#f05b8d",
269 "name_function": "#b675f1",
270 "literal_string": "#58c760",
271 "comment": "#a1a1a1 italic"
272 }
273 }
274 }
275}
276```
277
278## Installing a user theme
279
280```bash
281mkdir -p "$ZOT_HOME/themes"
282cp my-theme.json "$ZOT_HOME/themes/my-theme.json"
283```
284
285Then open `/settings` and choose **color theme**. zot switches theme
286immediately and persists the selection in `$ZOT_HOME/config.json`.
287
288If a selected theme file is deleted, zot resets the setting to the
289built-in auto/default theme.
290
291## Theme-only extensions
292
293A zot extension can exist only to ship a theme. No slash command,
294subprocess, or executable is required when the extension contains a
295valid theme file.
296
297Layout:
298
299```text
300$ZOT_HOME/extensions/my-theme-extension/
301├── extension.json
302└── theme.json
303```
304
305or:
306
307```text
308$ZOT_HOME/extensions/my-theme-extension/
309├── extension.json
310└── themes/
311 └── theme.json
312```
313
314`extension.json` for a theme-only extension:
315
316```json
317{
318 "name": "my-theme-extension",
319 "version": "1.0.0",
320 "description": "Ships a zot color theme",
321 "enabled": true
322}
323```
324
325No `exec` is needed when `theme.json` or `themes/theme.json` exists.
326If `exec` is present, zot treats it as a normal extension too.
327
328In `/settings → color theme`, extension themes show source info in
329the description, e.g. `from extension my-theme-extension — ...`.
330
331## Recommendations
332
333- Prefer short descriptions; settings wraps them, but concise is
334 easier to scan.
335- Keep spinner frames single-width when possible.
336- For theme packages, use a unique extension name so the source label
337 is clear.
338- For user-editable themes, keep comments out of JSON; JSON comments
339 are not supported.
340- Validate with `python3 -m json.tool theme.json` before installing.