Themes
A theme changes how Amee looks — colors, blur, corners, shadows — without touching layout or behavior. It’s a single .json file, no code, applied instantly and safely. Themes and skins are orthogonal: whichever skin is active can read the current theme’s tokens via amee.getToken(), so switching your color theme recolors a well-behaved skin too.
What a theme is
Section titled “What a theme is”A .json file with a little metadata plus a flat map of CSS custom-property (“token”) values. The app applies them directly onto :root — the stylesheet reads every token via var(--token, fallback), so a theme only needs to set the tokens it actually wants to change. Anything it omits falls back to the built-in default.
{ "$schema": "https://amee.thiennguyen.dev/theme.schema.json", "id": "midnight-amber", "name": "Midnight Amber", "author": "your-name", "description": "Warm amber accent on near-black glass.", "tokens": { "--accent": "#e8a33d", "--bg": "linear-gradient(165deg, #14100a 0%, #0a0806 100%)" }}That’s a complete, valid theme — it overrides two tokens and inherits everything else from the built-in default.
Installing a theme
Section titled “Installing a theme”There is currently no theme picker in the Dashboard. The engine below is live — the tokens are applied to every surface, and
amee.getToken()reads them — but the import/export/delete UI is not mounted in this build. Until it returns, installing a theme means putting the file in place yourself.
Three built-in themes ship inside the app bundle: default, ocean, and sunset. To add your own:
- Drop
<id>.jsoninto~/Library/Application Support/dev.thiennguyen.amee/themes/— the filename must match the theme’s ownid. - Set
"active_theme": "<id>"insettings.json, in that same directory. - Restart Amee.
default is the fallback whenever the active theme can’t be found, so a typo in either step recolors nothing rather than breaking anything.
The token reference
Section titled “The token reference”Every token is optional — set only what you want to change.
| Token | Controls | Example value |
|---|---|---|
--font |
Font stack for all text | -apple-system, "SF Pro Text", sans-serif |
--text |
Primary text color | #f3f1fa |
--text-muted |
Secondary/dim text (artist name, captions) | rgba(243, 241, 250, 0.62) |
--accent |
Buttons, the play button, active states, progress bar fill, and the visualizer’s bar color | #8b7cff |
--accent-text |
Text/icon color drawn on top of a solid --accent fill (e.g. the play button’s glyph) |
#ffffff |
--danger |
Error text | #ff6b6b |
--warning |
Cautionary callouts that aren’t errors (e.g. the skin install prompt’s unsandboxed-access notice) | #e0a83a |
--surface |
Background of glass panels (the mini-player pill, dashboard cards) | rgba(30, 28, 46, 0.55) |
--surface-hover |
Panel background on hover | rgba(46, 43, 68, 0.65) |
--surface-active |
Panel background while pressed; also the progress bar’s empty track and artwork placeholder | rgba(20, 19, 32, 0.7) |
--border |
1px border around glass panels | rgba(255, 255, 255, 0.16) |
--blur |
Backdrop blur radius behind glass panels | 28px |
--radius |
Corner radius for cards/buttons | 16px |
--radius-pill |
Corner radius for the mini-player pill and round icon buttons | 999px |
--shadow |
Drop shadow under glass panels | 0 4px 18px rgba(0, 0, 0, 0.3) |
--bg |
Dashboard window backdrop (not used by the mini player, which stays transparent) | a linear-gradient(...) or radial-gradient(...) |
A value can be anything valid in that CSS position — a hex/rgb/hsl color, a gradient, a length, a font stack. Nothing about the engine assumes a “glass” look specifically — that’s just what the bundled themes (default, ocean, sunset) happen to go for. A theme with --blur: 0px and an opaque --surface gets a flat, solid look instead.
About --shadow: the mini-player window is sized with just enough margin around the visible pill for its shadow to fade out. A shadow with a much bigger blur radius than the default will get clipped into a hard edge at the window boundary — keep the blur radius modest (under ~20px) if you’re overriding it.
Reading tokens from a skin
Section titled “Reading tokens from a skin”Skin authors can read any token’s current resolved value at runtime:
const accent = amee.getToken("--accent"); // e.g. "#8b7cff"This lets a skin adapt to whatever theme the user has chosen — extracting colors for a canvas-drawn visualizer, for example, or applying them to dynamically created elements. See the SDK reference.
Validation rules
Section titled “Validation rules”Enforced on import — a theme that violates these is rejected with an error, not silently truncated:
idandnamemust be present and non-empty (after trimming whitespace).tokensmust have at least 1 entry and at most 200.- Every token key must start with
--. - Every token value is a string, at most 2048 characters, and must not contain
{,},;,<, or>(these could break out of a CSS declaration).
Unknown tokens are ignored, not rejected — themes don’t need a version number to stay forward-compatible with future versions of Amee.
JSON Schema for editor tooling
Section titled “JSON Schema for editor tooling”Point your editor at the theme schema for inline validation and autocomplete by including a "$schema" key:
{ "$schema": "https://amee.thiennguyen.dev/theme.schema.json", "id": "my-theme", "name": "My Theme", "tokens": { "--accent": "#8b7cff" }}Design notes
Section titled “Design notes”- Tokens, not stylesheets. A theme can’t ship raw CSS, code, or override arbitrary selectors — only the fixed set of named tokens. This is what makes a theme file safe to load from an untrusted source: the worst a theme can do is look bad, never break layout or execute code. (A skin is a different trust story — see Skins.)
- Two windows, one source of truth. The Dashboard and the floating mini player are separate windows; changing the active theme updates both together.