Theme Engine
CSS variable-based theming with built-in presets, live editing, and .soundie-theme file format.
Overview
The Soundie theme engine sets CSS custom properties on document.documentElement at startup and whenever the theme changes. Every color is stored as an RGB triplet (e.g. 139 92 246) so you can compose them with arbitrary opacity using rgb(var(--color-accent) / 0.15).
Themes are defined as .soundie-theme JSON files — a superset of a standard JSON schema that includes colors, glass settings, typography, layout dimensions, and motion speed.
Built-in Presets
Dark
Deep charcoal + violet
Midnight
Navy blue + indigo
Aurora
Dark teal + northern lights
Light
Crisp white + violet
Sunset
Warm amber + coral
Neon
Electric lime + cyberpunk
Rose
Deep rose + crimson
Ocean
Electric cyan + navy
CSS Variables Reference
| Variable | Description |
|---|---|
| --color-surface | Base background color (RGB triplet) |
| --color-surface-raised | Slightly elevated surfaces — cards, panels |
| --color-surface-overlay | Modal, dropdown, tooltip backgrounds |
| --color-surface-sunken | Inset areas — sidebar, player bar |
| --color-accent | Primary brand color (RGB triplet) |
| --color-accent-hover | Accent on hover |
| --color-accent-active | Accent on press/active |
| --color-text-primary | Main text color |
| --color-text-secondary | Subdued labels |
| --color-text-tertiary | Faint hints, placeholders |
| --color-text-disabled | Disabled state text |
| --color-border-subtle | Hairline dividers |
| --color-border | Default border |
| --color-border-strong | Emphasized border, focus ring |
| --color-success | Success / live indicators |
| --color-warning | Warnings |
| --color-error | Errors and destructive actions |
| --color-player-bg | Player bar background |
| --color-player-waveform | Waveform track (unplayed) |
| --color-player-waveform-played | Waveform played portion |
| --glass-blur-radius | Backdrop blur in px |
| --glass-transparency | Surface transparency 0–1 |
| --glass-border-opacity | Border visibility 0–1 |
| --glass-frost-intensity | Frost/noise overlay intensity |
| --font-sans | UI font family stack |
| --font-mono | Monospace font stack |
| --font-display | Display / heading font |
| --layout-sidebar-width | Sidebar width in px |
| --layout-player-height | Player bar height in px |
| --motion-speed | Animation speed multiplier |
Usage in CSS / Tailwind
usage.css
css
1/* Use accent with opacity */
2.my-button {
3 background: rgb(var(--color-accent) / class="token-number">0.15);
4 color: rgb(var(--color-accent));
5 border: 1px solid rgb(var(--color-accent) / class="token-number">0.3);
6}
7
8/* Glass panel */
9.panel {
10 background: rgb(var(--color-surface-raised) / var(--glass-transparency));
11 backdrop-filter: blur(var(--glass-blur-radius));
12 border: 1px solid rgb(class="token-number">255 class="token-number">255 class="token-number">255 / var(--glass-border-opacity));
13}
14
15/* Responsive to theme changes automatically */
16[data-theme="light"] .panel {
17 /* No overrides needed — CSS vars update globally */
18}
usage-in-tailwind.tsx
ts
1// In Tailwind, use arbitrary values with CSS vars
2<div className="bg-[rgb(var(--color-surface-raised))]
3 border-[rgb(var(--color-border))]
4 text-[rgb(var(--color-text-primary))]">
5
6 <button className="bg-[rgb(var(--color-accent)/class="token-number">0.15)]
7 text-[rgb(var(--color-accent))]
8 hover:bg-[rgb(var(--color-accent)/class="token-number">0.25)]">
9 Accent Button
10 </button>
11</div>
.soundie-theme File Format
my-theme.soundie-theme
json
1{
2 class="token-prop">"$schema": "https://spotx.app/theme-schema/v1",
3 class="token-prop">"id": class="token-prop">"550e8400-e29b-41d4-a716-class="token-number">446655440000",
4 class="token-prop">"name": class="token-prop">"Cyberpunk class="token-number">2077",
5 class="token-prop">"description": class="token-prop">"Night City vibes with electric yellow on black.",
6 class="token-prop">"author": class="token-prop">"you",
7 class="token-prop">"version": class="token-prop">"class="token-number">1.0.class="token-number">0",
8 class="token-prop">"base": class="token-prop">"dark",
9 class="token-prop">"isBuiltin": class="token-keyword">false,
10 class="token-prop">"colors": {
11 class="token-prop">"surface": { class="token-prop">"base": "#0A0A0A", "raised": "#141414", "overlay": "#1E1E1E", "sunken": "#050505" },
12 class="token-prop">"accent": { class="token-prop">"primary": "#F0E614", "primaryHover": "#F5EE50", "primaryActive": "#C4BC06", "secondary": "#FF2D78" },
13 class="token-prop">"text": { class="token-prop">"primary": "#F0F0E0", "secondary": "#A0A080", "tertiary": "#606050", "disabled": "#303025", "inverse": "#0A0A0A" },
14 class="token-prop">"border": { class="token-prop">"subtle": "#1A1A10", "default": "#2A2A1A", "strong": "#404030" },
15 class="token-prop">"status": { class="token-prop">"success": "#00FF41", "warning": "#F0E614", "error": "#FF2D78", "info": "#00BFFF" },
16 class="token-prop">"player": { class="token-prop">"background": "#080808", "waveform": "#1E1E10", "waveformPlayed": "#F0E614" }
17 },
18 class="token-prop">"glass": { class="token-prop">"blurRadius": class="token-number">24, class="token-prop">"transparency": class="token-number">0.65, class="token-prop">"borderOpacity": class="token-number">0.08, class="token-prop">"frostIntensity": class="token-number">0.4, class="token-prop">"noiseAmount": class="token-number">0.05 },
19 class="token-prop">"typography": { class="token-prop">"fontFamilyUi": : class="token-string">"\class="token-prop">"Share Tech Mono\", monospace", class="token-prop">"fontFamilyMono": : class="token-string">"\class="token-prop">"Share Tech Mono\", monospace", class="token-prop">"fontFamilyDisplay": : class="token-string">"\class="token-prop">"Share Tech Mono\", monospace" },
20 class="token-prop">"layout": { class="token-prop">"sidebarWidthPx": class="token-number">220, class="token-prop">"playerHeightPx": class="token-number">88, class="token-prop">"panelWidthPx": class="token-number">300 },
21 class="token-prop">"motion": { class="token-prop">"speedMultiplier": class="token-number">0.8 },
22 class="token-prop">"vibrancy": { class="token-prop">"macOs": class="token-prop">"HudWindow", class="token-prop">"windows": : class="token-string">"acrylic" }
23}
ThemeEngine API
ThemeEngine.ts
ts
1import { ThemeEngine } from "@soundie/theme-engine";
2
3// Apply a full theme to the document root
4ThemeEngine.apply(theme);
5
6// Apply only colors (e.g. after a color picker change)
7ThemeEngine.applyColors(theme.colors);
8
9// Apply only glass settings
10ThemeEngine.applyGlass(theme.glass);
11
12// Get the full CSS property map without applying
13const props = ThemeEngine.buildPropertyMap(theme);
14// { "--color-accent": "139 92 246", ... }
useTheme.ts
ts
1import { useTheme } from "@soundie/theme-engine";
2
3function MyComponent() {
4 const {
5 theme, // Current ThemeDefinition
6 allThemes, // All themes (presets + custom)
7 setTheme, // (id: string) => Promise<void>
8 importTheme, // (json: string) => Promise<ThemeDefinition>
9 exportTheme, // (id: string) => string (JSON)
10 deleteTheme, // (id: string) => Promise<void>
11 updateColors, // (patch: Partial<ColorPalette>) => Promise<void>
12 updateGlass, // (patch: Partial<GlassConfig>) => Promise<void>
13 updateTypography,// (patch: Partial<TypographyConfig>) => Promise<void>
14 updateLayout, // (patch: Partial<LayoutConfig>) => Promise<void>
15 } = useTheme();
16}