Skip to content

Custom Themes

MiniFy supports fully customizable themes. This guide walks you through creating your own theme from scratch, understanding the theme system, and sharing themes with the community.

┌───────────────────────────────────────────────────────────────────────────────┐
│ Theme System Architecture │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Theme JSON File (RawTheme) │ │
│ │ │ │
│ │ { │ │
│ │ "name": "Custom Theme", │ │
│ │ "panel": { "background": "#1a1b26", "borderRadius": 18 }, │ │
│ │ "controls": { "iconColor": "#fff", "iconColorActive": "#7aa2f7" }, │ │
│ │ "playbar": { "trackBg": "...", "trackFill": "..." }, │ │
│ │ "typography": { "songTitle": {...}, "songArtist": {...} }, │ │
│ │ "settings": { "panelBg": "...", "accent": "..." } │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Theme Loader (themeLoader.ts) │ │
│ │ │ │
│ │ transformTheme(RawTheme) → ThemeConfig → applyThemeConfig() │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ CSS Custom Properties │ │
│ │ │ │
│ │ :root { │ │
│ │ --player-panel-bg: #1a1b26; │ │
│ │ --player-controls-color: #c0caf5; │ │
│ │ --player-playbar-track-fill: linear-gradient(...); │ │
│ │ --settings-accent: #7aa2f7; │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ UI Components │ │
│ │ │ │
│ │ Components use CSS variables for all colors │ │
│ │ Theme changes are instant (no rebuild needed) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────────┘

Built-in themes are bundled with the application:

apps/desktop/src/themes/
├── dark.json
├── light.json
├── catppuccin.json
├── dracula.json
├── chatgpt.json
├── youtube.json
├── bmw.json
└── milka.json

Custom themes should be placed in the user themes directory:

%APPDATA%\MiniFy\themes\

Example: C:\Users\YourName\AppData\Roaming\MiniFy\themes\

  1. Create the themes directory

    Terminal window
    New-Item -ItemType Directory -Force "$env:APPDATA\MiniFy\themes"
  2. Create your theme file

    Create a new JSON file (e.g., my-theme.json):

    {
    "name": "My Custom Theme",
    "panel": {
    "background": "#1e1e2e",
    "borderRadius": 18,
    "shadow": "0 14px 40px rgba(0,0,0,0.55)"
    },
    "controls": {
    "iconColor": "#cdd6f4",
    "iconColorActive": "#89b4fa"
    },
    "playbar": {
    "trackBg": "rgba(205,214,244,0.16)",
    "trackFill": "linear-gradient(90deg, #89b4fa 0%, #f5c2e7 100%)",
    "thumbColor": "#cdd6f4",
    "timeTextColor": "#6c7086"
    },
    "typography": {
    "songTitle": { "color": "#cdd6f4" },
    "songArtist": { "color": "#a6adc8" }
    },
    "actions": {
    "iconColor": "#cdd6f4",
    "iconBackground": "rgba(205,214,244,0.04)",
    "iconBackgroundHover": "rgba(205,214,244,0.10)"
    },
    "cover": {
    "borderColor": "rgba(205,214,244,0.20)",
    "borderRadius": 12
    },
    "settings": {
    "panelBg": "#313244",
    "panelBorder": "#45475a",
    "text": "#cdd6f4",
    "textMuted": "#6c7086",
    "accent": "#89b4fa"
    }
    }
  3. Restart MiniFy

    Close and reopen MiniFy to load the new theme.

  4. Select your theme

    Right-click → Themes → Select “My Custom Theme”

The theme system uses a component-based structure matching RawTheme in themeLoader.ts:

{
"name": "Theme Name",
"panel": {
"background": "#0a0a0a",
"borderRadius": 18,
"shadow": "0 14px 40px rgba(0,0,0,0.55)"
},
"controls": {
"iconColor": "#ffffff",
"iconColorActive": "#1DB954"
},
"playbar": {
"trackBg": "rgba(255,255,255,0.16)",
"trackFill": "linear-gradient(90deg, #1DB954 0%, #1ed760 100%)",
"thumbColor": "#ffffff",
"timeTextColor": "#b0b0b0"
},
"typography": {
"songTitle": { "color": "#ffffff" },
"songArtist": { "color": "#b0b0b0" }
},
"actions": {
"iconColor": "#ffffff",
"iconBackground": "rgba(255,255,255,0.04)",
"iconBackgroundHover": "rgba(255,255,255,0.10)"
},
"cover": {
"borderColor": "rgba(255,255,255,0.20)",
"borderRadius": 12
},
"settings": {
"panelBg": "#0a0a0a",
"panelBorder": "#ffffff1a",
"text": "#ffffff",
"textMuted": "#b0b0b0",
"headerText": "#ffffff",
"itemHover": "#ffffff14",
"itemActive": "#ffffff24",
"accent": "#1DB954"
}
}
FieldTypeDescription
namestringDisplay name in theme menu (required)
PropertyTypeCSS VariableDescription
panel.backgroundstring--player-panel-bgMain panel background
panel.borderRadiusnumber--player-panel-radiusPanel corner radius (px)
panel.shadowstring--player-panel-shadowPanel box shadow
PropertyTypeCSS VariableDescription
controls.iconColorstring--player-controls-colorControl icons color
controls.iconColorActivestring--player-controls-color-activeActive control color
PropertyTypeCSS VariableDescription
playbar.trackBgstring--player-playbar-track-bgProgress track background
playbar.trackFillstring--player-playbar-track-fillProgress fill (supports gradients)
playbar.thumbColorstring--player-playbar-thumb-colorScrubber thumb color
playbar.timeTextColorstring--player-playbar-time-colorTime display color
PropertyTypeCSS VariableDescription
typography.songTitle.colorstring--player-song-title-colorSong title color
typography.songArtist.colorstring--player-song-artist-colorArtist name color
PropertyTypeCSS VariableDescription
actions.iconColorstring--player-actions-colorAction icons color
actions.iconBackgroundstring--player-actions-bgAction button background
actions.iconBackgroundHoverstring--player-actions-bg-hoverAction button hover state
PropertyTypeCSS VariableDescription
cover.borderColorstring--player-cover-border-colorAlbum art border
cover.borderRadiusnumber--player-cover-radiusAlbum art corner radius (px)
PropertyTypeCSS VariableDescription
settings.panelBgstring--settings-panel-bgSettings panel background
settings.panelBorderstring--settings-panel-borderSettings panel border
settings.textstring--settings-textPrimary text color
settings.textMutedstring--settings-text-mutedMuted text color
settings.headerTextstring--settings-header-textSection header color
settings.itemHoverstring--settings-item-hoverList item hover state
settings.itemActivestring--settings-item-activeList item active state
settings.accentstring--settings-accentAccent color for toggles

Colors can be specified in multiple formats:

{
"panel": {
"background": "#0a0a0a"
},
"controls": {
"iconColor": "rgb(255, 255, 255)",
"iconColorActive": "hsl(142, 76%, 36%)"
},
"playbar": {
"trackFill": "linear-gradient(90deg, #1DB954 0%, #1ed760 100%)",
"trackBg": "rgba(255, 255, 255, 0.16)"
}
}

A popular theme with cool, arctic colors:

{
"name": "Nord",
"panel": {
"background": "#2e3440",
"borderRadius": 18,
"shadow": "0 14px 40px rgba(0,0,0,0.55)"
},
"controls": {
"iconColor": "#eceff4",
"iconColorActive": "#88c0d0"
},
"playbar": {
"trackBg": "rgba(236,239,244,0.16)",
"trackFill": "linear-gradient(90deg, #88c0d0 0%, #81a1c1 100%)",
"thumbColor": "#eceff4",
"timeTextColor": "#4c566a"
},
"typography": {
"songTitle": { "color": "#eceff4" },
"songArtist": { "color": "#d8dee9" }
},
"actions": {
"iconColor": "#eceff4",
"iconBackground": "rgba(236,239,244,0.04)",
"iconBackgroundHover": "rgba(236,239,244,0.10)"
},
"cover": {
"borderColor": "rgba(236,239,244,0.20)",
"borderRadius": 12
},
"settings": {
"panelBg": "#3b4252",
"panelBorder": "#434c5e",
"text": "#eceff4",
"textMuted": "#d8dee9",
"accent": "#88c0d0"
}
}

A popular developer theme:

{
"name": "Tokyo Night",
"panel": {
"background": "#1a1b26",
"borderRadius": 18,
"shadow": "0 14px 40px rgba(0,0,0,0.65)"
},
"controls": {
"iconColor": "#c0caf5",
"iconColorActive": "#7aa2f7"
},
"playbar": {
"trackBg": "rgba(192,202,245,0.12)",
"trackFill": "linear-gradient(90deg, #7aa2f7 0%, #bb9af7 100%)",
"thumbColor": "#c0caf5",
"timeTextColor": "#565f89"
},
"typography": {
"songTitle": { "color": "#c0caf5" },
"songArtist": { "color": "#9aa5ce" }
},
"actions": {
"iconColor": "#c0caf5",
"iconBackground": "rgba(192,202,245,0.04)",
"iconBackgroundHover": "rgba(192,202,245,0.10)"
},
"cover": {
"borderColor": "rgba(192,202,245,0.20)",
"borderRadius": 12
},
"settings": {
"panelBg": "#24283b",
"panelBorder": "#414868",
"text": "#c0caf5",
"textMuted": "#565f89",
"accent": "#7aa2f7"
}
}

A retro groove theme:

{
"name": "Gruvbox Dark",
"panel": {
"background": "#282828",
"borderRadius": 18,
"shadow": "0 14px 40px rgba(0,0,0,0.55)"
},
"controls": {
"iconColor": "#ebdbb2",
"iconColorActive": "#b8bb26"
},
"playbar": {
"trackBg": "rgba(235,219,178,0.16)",
"trackFill": "linear-gradient(90deg, #b8bb26 0%, #fabd2f 100%)",
"thumbColor": "#ebdbb2",
"timeTextColor": "#928374"
},
"typography": {
"songTitle": { "color": "#ebdbb2" },
"songArtist": { "color": "#d5c4a1" }
},
"actions": {
"iconColor": "#ebdbb2",
"iconBackground": "rgba(235,219,178,0.04)",
"iconBackgroundHover": "rgba(235,219,178,0.10)"
},
"cover": {
"borderColor": "rgba(235,219,178,0.20)",
"borderRadius": 12
},
"settings": {
"panelBg": "#3c3836",
"panelBorder": "#504945",
"text": "#ebdbb2",
"textMuted": "#928374",
"accent": "#b8bb26"
}
}

The classic Solarized theme:

{
"name": "Solarized Dark",
"panel": {
"background": "#002b36",
"borderRadius": 18,
"shadow": "0 14px 40px rgba(0,0,0,0.55)"
},
"controls": {
"iconColor": "#839496",
"iconColorActive": "#268bd2"
},
"playbar": {
"trackBg": "rgba(131,148,150,0.16)",
"trackFill": "linear-gradient(90deg, #268bd2 0%, #2aa198 100%)",
"thumbColor": "#839496",
"timeTextColor": "#586e75"
},
"typography": {
"songTitle": { "color": "#93a1a1" },
"songArtist": { "color": "#839496" }
},
"actions": {
"iconColor": "#839496",
"iconBackground": "rgba(131,148,150,0.04)",
"iconBackgroundHover": "rgba(131,148,150,0.10)"
},
"cover": {
"borderColor": "rgba(131,148,150,0.20)",
"borderRadius": 12
},
"settings": {
"panelBg": "#073642",
"panelBorder": "#073642",
"text": "#839496",
"textMuted": "#586e75",
"accent": "#268bd2"
}
}
┌───────────────────────────────────────────────────────────────────────────────┐
│ Contrast Recommendations │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Background vs Foreground: │
│ ───────────────────────── │
│ • Minimum contrast ratio: 4.5:1 (WCAG AA) │
│ • Recommended: 7:1 or higher (WCAG AAA) │
│ │
│ Primary vs Background: │
│ ────────────────────── │
│ • Should be visually distinct │
│ • Used for interactive elements │
│ │
│ Muted vs Background: │
│ ──────────────────── │
│ • Should be readable but subdued │
│ • Minimum contrast ratio: 3:1 │
│ │
└────────────────────────────────────────────────────────────────────────────────┘

Before sharing your theme, test these scenarios:

  • Track title is readable
  • Artist name is readable but subdued
  • Progress bar is clearly visible
  • Buttons have clear hover states
  • Active/inactive states are distinguishable
  • Works in all three layouts
  • Settings page is usable
  • AI DJ chat is readable
  1. Right-click → Settings → Themes
  2. View all themes (built-in + custom)
  3. Delete custom themes
  4. Export themes for sharing

MiniFy validates themes on load. Invalid themes are skipped with a console warning.

Common validation errors:

  • Missing name field
  • Missing colors object
  • Invalid color format
  • JSON syntax errors

Open DevTools (F12) to see theme loading errors:

// Console output
[Theme] Loading theme: my-theme.json
[Theme] Error: Invalid color format for 'primary'
  1. Fork the repository

    Fork on GitHub

  2. Add your theme

    Add the JSON file to apps/desktop/src/themes/

  3. Add to website preview

    Also add to apps/www/public/themes/

  4. Update theme index

    Add your theme to apps/www/public/themes/index.json

  5. Submit a Pull Request

    Include screenshots showing your theme in use

You can share theme JSON files directly:

  1. Export your theme from Settings
  2. Share the JSON file
  3. Recipients place it in their themes directory

All theme properties are mapped to CSS custom properties:

:root {
/* Panel */
--player-panel-bg: #0a0a0a;
--player-panel-radius: 18px;
--player-panel-shadow: 0 14px 40px rgba(0,0,0,0.55);
/* Controls */
--player-controls-color: #ffffff;
--player-controls-color-active: #1DB954;
/* Playbar */
--player-playbar-track-bg: rgba(255,255,255,0.16);
--player-playbar-track-fill: linear-gradient(90deg, #1DB954 0%, #1ed760 100%);
--player-playbar-thumb-color: #ffffff;
--player-playbar-time-color: #b0b0b0;
/* Typography */
--player-song-title-color: #ffffff;
--player-song-artist-color: #b0b0b0;
/* Actions */
--player-actions-color: #ffffff;
--player-actions-bg: rgba(255,255,255,0.04);
--player-actions-bg-hover: rgba(255,255,255,0.10);
/* Cover */
--player-cover-border-color: rgba(255,255,255,0.20);
--player-cover-radius: 12px;
/* Settings Panel */
--settings-panel-bg: #0a0a0a;
--settings-panel-border: #ffffff1a;
--settings-text: #ffffff;
--settings-text-muted: #b0b0b0;
--settings-header-text: #ffffff;
--settings-item-hover: #ffffff14;
--settings-item-active: #ffffff24;
--settings-accent: #1DB954;
}
  1. Check the file is valid JSON
  2. Verify it’s in the correct directory
  3. Ensure the file has a .json extension
  4. Restart MiniFy
  1. Check for typos in color property names
  2. Verify color format is valid
  3. Check DevTools console for errors
  1. Ensure adequate contrast between colors
  2. Test all UI elements
  3. Try adjusting problematic colors