Custom themes

July 13, 2026 · View on GitHub

Drop a JSON file in termic's themes folder and it shows up in the theme picker as a first-class theme: chrome and terminal, coupled, exactly like the built-ins. There is no theme editor UI. The files are the interface, which makes themes trivially shareable and stowable from dotfiles.

Where

~/.config/termic/themes/

($XDG_CONFIG_HOME/termic/themes/ if you set that variable. Same path on every platform; release and dev builds share it.)

Themes are hand-authored, shareable config, so they live under .config like other terminal tools (wezterm, starship, kitty), not in the app-owned Application Support data dir, which makes them trivially stowable from dotfiles.

When the folder is empty, termic seeds it with a README.md and an example.json.sample (a complete theme, ready to cp to my-theme.json). Neither ends in .json, so the picker ignores both. Delete them once you have your own theme; they only come back if the folder is emptied. The sources live in src-tauri/assets/themes/.

One JSON file per theme. The picker's "Open themes folder" row opens this directory (and creates it if needed). Themes are read at startup and re-read every time the picker opens, so the edit loop is: save the file, hover the picker, done. No restart, no watcher.

Picking a theme

Two ways, same as the built-ins:

  • Hover the theme icon (sun/moon) in the title bar. Custom themes list below the built-ins, with an "Open themes folder" row at the bottom.
  • ⇧⌘P, then "Change theme…". Arrow keys live-preview each theme; Enter commits, Escape rolls back to what you had.

The keyboard theme-cycle shortcut only cycles System / Light / Dark, so a custom theme sticks until you explicitly pick something else.

The theme's id is its file name (a file named rose-pine-moon.json becomes custom:rose-pine-moon internally), so a file named claude.json can never shadow the built-in Claude theme.

File format

{
  "name": "Rosé Pine Moon",
  "colorScheme": "dark",
  "ui": {
    "bg": "#232136", "bg-1": "#2a273f", "bg-2": "#393552", "bg-3": "#44415a",
    "fg": "#e0def4", "fg-dim": "#908caa", "fg-faint": "#6e6a86",
    "border": "#56526e", "border-soft": "#393552",
    "hover": "rgba(224,222,244,0.05)", "sel": "rgba(196,167,231,0.22)",
    "accent": "#c4a7e7", "accent-soft": "rgba(196,167,231,0.22)", "accent-deep": "#6f5b9e",
    "accent-fg": "#232136",
    "ok": "#9ccfd8", "ok-fg": "#232136", "warn": "#f6c177", "err": "#eb6f92",
    "palette-red": "#eb6f92", "palette-orange": "#e89f6f", "palette-yellow": "#f6c177",
    "palette-green": "#97c9a3", "palette-teal": "#9ccfd8", "palette-blue": "#3e8fb0",
    "palette-purple": "#c4a7e7", "palette-pink": "#d8a0c9"
  },
  "terminal": {
    "background": "#232136", "foreground": "#e0def4",
    "cursor": "#c4a7e7", "selectionBackground": "#44415a",
    "black": "#393552", "red": "#eb6f92", "green": "#3e8fb0", "yellow": "#f6c177",
    "blue": "#9ccfd8", "magenta": "#c4a7e7", "cyan": "#ea9a97", "white": "#e0def4",
    "brightBlack": "#6e6a86", "brightRed": "#eb6f92", "brightGreen": "#3e8fb0",
    "brightYellow": "#f6c177", "brightBlue": "#9ccfd8", "brightMagenta": "#c4a7e7",
    "brightCyan": "#ea9a97", "brightWhite": "#e0def4"
  }
}

Top-level fields

FieldRequiredValuesNotes
nameyesstringPicker label. Falls back to the file name if empty.
colorSchemeyes"dark" or "light"Drives form controls, the editor's auto syntax theme, and the COLORFGBG env var agents use to pick their TUI theme. Anything other than "light" is treated as "dark".
uinoobjectChrome colors, see below.
terminalnoobjectxterm palette, see below.

Unknown top-level fields are ignored (forward compatibility).

ui keys

Every key is optional and maps to the --color-<key> CSS variable. Missing keys fall back to the built-in palette matching your colorScheme (Dark+ for dark themes, Light for light ones), so a minimal theme can override just bg and accent and still look coherent.

KeyWhat it paints
bgmain content area
bg-1chrome: sidebar, title bar, tabs
bg-2nested cards, badges
bg-3hover and active surfaces
fgprimary text
fg-dimmuted text
fg-faintfaint icons, secondary text
borderstandard borders
border-softhairline borders
hoverhover overlay (usually a low-alpha rgba)
selselection tint
accentbrand accent (buttons, active states)
accent-softaccent-tinted backgrounds (usually low-alpha rgba)
accent-deepaccent for filled buttons
accent-fgink on a solid accent fill (count badges, filled CTAs). Defaults to white; set a dark ink if your accent is light
oksuccess green
ok-fgink on a solid ok fill (toggle knobs). Defaults to white
warnwarning yellow
errerror red
palette-redpalette-pinkhue-named accent ramp for user-assigned marks (today: project-group folder colors). 8 keys: palette-red, palette-orange, palette-yellow, palette-green, palette-teal, palette-blue, palette-purple, palette-pink. Tune shades to your theme, but keep each hue recognizable: users pick colors by name and expect "red" to stay red across themes

The --color-cli-* agent tint variables are not themeable in v1; they keep their defaults.

terminal keys

Standard xterm.js ITheme keys, all optional: background, foreground, cursor, cursorAccent, selectionBackground, selectionForeground, selectionInactiveBackground, the ANSI 8 (black through white), and the bright ANSI 8 (brightBlack through brightWhite).

Missing keys fall back to the built-in palette matching your colorScheme (Dark+ for dark themes, Light for light ones), so a partial block still yields a complete, readable ANSI 16.

Color values

Hex (#rgb, #rgba, #rrggbb, #rrggbbaa) and rgb() / rgba() / hsl() / hsla() notation. Named CSS colors are not accepted.

Error handling

Nothing about a theme file can break the app:

  • A file that fails to parse is skipped (the rest still list). Check the dev console / stderr for a [themes] skipping ... line.
  • An unknown key or a malformed color value is dropped; the key falls back to its default.
  • Deleting the active theme's file falls back to the Claude theme on the next picker open or launch.

Notes

  • Two files with the same name both list (their ids differ). The picker shows whatever the files say.
  • The active custom theme's payload is cached in localStorage so the first paint after launch uses it directly. The folder is re-read right after startup and reconciled (edits re-apply, deletions fall back).