Create a Custom Theme
Magia’s theme system is fully open. Any .json file you drop into ~/.magia/themes/ becomes a selectable theme in Settings → Appearance. Changes are hot-reloaded — you do not need to restart the app while iterating.
How themes are loaded
Section titled “How themes are loaded”At startup, Magia reads every .json file from ~/.magia/themes/. The file name (without the .json extension) becomes the theme’s ID. The directory is created automatically on first launch, so it always exists.
A file system watcher (ThemeWatcher in the Rust backend) monitors the directory using the notify crate. Any create, modify, or rename event triggers a debounced reload after 500 ms. The new theme list is pushed to the frontend via a themes:changed Tauri event, and your theme appears in the picker immediately.
Minimal example
Section titled “Minimal example”Save this as ~/.magia/themes/my-theme.json:
{ "name": "My Theme", "author": "your-name", "version": 1, "modes": { "dark": { "background": "224 71% 4%", "foreground": "213 31% 91%", "primary": "210 40% 98%", "primary-foreground": "222.2 47.4% 1.2%", "secondary": "222.2 47.4% 11.2%", "secondary-foreground": "210 40% 98%", "muted": "223 47% 11%", "muted-foreground": "215.4 16.3% 56.9%", "accent": "216 34% 17%", "accent-foreground": "210 40% 98%", "card": "224 71% 4%", "card-foreground": "213 31% 91%", "popover": "224 71% 4%", "popover-foreground": "215 20.2% 65.1%", "border": "216 34% 17%", "input": "216 34% 17%", "ring": "216 34% 17%", "destructive": "0 63% 31%", "destructive-foreground": "210 40% 98%", "sidebar": "224 71% 4%", "sidebar-foreground": "213 31% 91%", "sidebar-primary": "210 40% 98%", "sidebar-primary-foreground": "222.2 47.4% 1.2%", "sidebar-accent": "216 34% 17%", "sidebar-accent-foreground": "210 40% 98%", "sidebar-border": "216 34% 17%", "sidebar-ring": "216 34% 17%", "chart-1": "220 70% 50%", "chart-2": "160 60% 45%", "chart-3": "30 80% 55%", "chart-4": "280 65% 60%", "chart-5": "340 75% 55%" }, "light": { "background": "0 0% 100%", "foreground": "222.2 47.4% 11.2%", "primary": "222.2 47.4% 11.2%", "primary-foreground": "210 40% 98%", "secondary": "210 40% 96.1%", "secondary-foreground": "222.2 47.4% 11.2%", "muted": "210 40% 96.1%", "muted-foreground": "215.4 16.3% 46.9%", "accent": "210 40% 96.1%", "accent-foreground": "222.2 47.4% 11.2%", "card": "0 0% 100%", "card-foreground": "222.2 47.4% 11.2%", "popover": "0 0% 100%", "popover-foreground": "222.2 47.4% 11.2%", "border": "214.3 31.8% 91.4%", "input": "214.3 31.8% 91.4%", "ring": "222.2 84% 4.9%", "destructive": "0 84.2% 60.2%", "destructive-foreground": "210 40% 98%", "sidebar": "210 40% 98%", "sidebar-foreground": "222.2 47.4% 11.2%", "sidebar-primary": "222.2 47.4% 11.2%", "sidebar-primary-foreground": "210 40% 98%", "sidebar-accent": "210 40% 96.1%", "sidebar-accent-foreground": "222.2 47.4% 11.2%", "sidebar-border": "214.3 31.8% 91.4%", "sidebar-ring": "222.2 84% 4.9%", "chart-1": "221 83% 53%", "chart-2": "212 95% 68%", "chart-3": "216 92% 60%", "chart-4": "210 98% 78%", "chart-5": "212 97% 87%" } }, "shape": { "radius": "0.5rem" }, "fonts": { "ui": "Inter", "mono": "JetBrains Mono" }}Open Settings → Appearance — your theme appears in the list under its name field. Select it to apply.
Color values
Section titled “Color values”All color values use the HSL channel format without the hsl() wrapper:
"<hue> <saturation>% <lightness>%"Example: "210 40% 98%" is equivalent to hsl(210, 40%, 98%). This format follows the shadcn/ui convention and is required because the CSS variable system appends opacity modifiers (e.g., hsl(var(--primary) / 0.5)).
Color tokens
Section titled “Color tokens”Your theme maps 34 semantic tokens:
| Token | Role |
|---|---|
background / foreground | Main app canvas and primary text |
card / card-foreground | Card component surfaces |
popover / popover-foreground | Dropdown menus and tooltips |
primary / primary-foreground | Primary action color (buttons, active states) |
secondary / secondary-foreground | Secondary surfaces and labels |
muted / muted-foreground | Subtle backgrounds and de-emphasized text |
accent / accent-foreground | Hover states and highlights |
destructive / destructive-foreground | Errors and destructive actions |
border | Default border color |
input | Input field border |
ring | Keyboard focus ring |
chart-1 through chart-5 | Data visualization palette |
sidebar / sidebar-foreground | Sidebar panel surface and text |
sidebar-primary / sidebar-primary-foreground | Active sidebar items |
sidebar-accent / sidebar-accent-foreground | Sidebar hover states |
sidebar-border / sidebar-ring | Sidebar border and focus ring |
You must provide all tokens if you want the theme to render correctly in every part of the UI. Omitted tokens fall back to the browser’s unset value, which usually means transparent or black.
Dark and light modes
Section titled “Dark and light modes”modes.dark and modes.light are independent color maps. Which map is active depends on the Appearance setting in Settings → Appearance:
- Dark — always uses
modes.dark. - Light — always uses
modes.light. - System — follows the macOS dark/light preference.
If you only define modes.dark, Magia uses the dark map for both dark and light modes. If you only care about dark mode, you can omit modes.light entirely.
shape.radius sets the --radius CSS variable used for border-radius throughout the UI. Use any valid CSS length:
"shape": { "radius": "0.5rem"}For a completely flat, borderless look use "0px". For rounder corners, try "0.75rem" or "1rem".
shape.shadows is optional and overrides up to four shadow levels: shadow-sm, shadow-md, shadow-lg, and shadow-xl. Each value is a raw CSS box-shadow string:
"shape": { "radius": "0.5rem", "shadows": { "shadow-sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)", "shadow-md": "0 4px 6px -1px rgb(0 0 0 / 0.1)" }}fonts.ui and fonts.mono override the UI and monospace fonts respectively. Values are CSS font-family strings. Fonts must already be installed on the system or loaded by a CSS @font-face rule — Magia does not download fonts.
"fonts": { "ui": "Berkeley Mono, monospace", "mono": "Berkeley Mono"}Both fields are optional. Omitting fonts.ui keeps the default (Noto Sans). Omitting fonts.mono keeps the default (Noto Sans Mono).
Iterating with hot-reload
Section titled “Iterating with hot-reload”- Open
~/.magia/themes/my-theme.jsonin any text editor. - Make a change — for example, adjust a color value.
- Save the file.
- Switch to Magia — within half a second, the
themes:changedevent fires and the theme list is refreshed. If your theme is currently selected, the new colors apply immediately without re-selecting.
This makes it fast to iterate: keep the file open in one editor and Magia running alongside it.
Sharing your theme
Section titled “Sharing your theme”A theme is a single JSON file with no external dependencies. To share it:
- Post the file in a GitHub Gist or repository.
- Tell others to save it to
~/.magia/themes/and select it in Settings → Appearance.
To distribute it through a Magia plugin, bundle the theme JSON as part of a plugin package — see Create a Plugin for details.