Skip to content

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.

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.

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.

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)).

Your theme maps 34 semantic tokens:

TokenRole
background / foregroundMain app canvas and primary text
card / card-foregroundCard component surfaces
popover / popover-foregroundDropdown menus and tooltips
primary / primary-foregroundPrimary action color (buttons, active states)
secondary / secondary-foregroundSecondary surfaces and labels
muted / muted-foregroundSubtle backgrounds and de-emphasized text
accent / accent-foregroundHover states and highlights
destructive / destructive-foregroundErrors and destructive actions
borderDefault border color
inputInput field border
ringKeyboard focus ring
chart-1 through chart-5Data visualization palette
sidebar / sidebar-foregroundSidebar panel surface and text
sidebar-primary / sidebar-primary-foregroundActive sidebar items
sidebar-accent / sidebar-accent-foregroundSidebar hover states
sidebar-border / sidebar-ringSidebar 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.

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).

  1. Open ~/.magia/themes/my-theme.json in any text editor.
  2. Make a change — for example, adjust a color value.
  3. Save the file.
  4. Switch to Magia — within half a second, the themes:changed event 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.

A theme is a single JSON file with no external dependencies. To share it:

  1. Post the file in a GitHub Gist or repository.
  2. 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.