Skip to content

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.

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.

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:

  1. Drop <id>.json into ~/Library/Application Support/dev.thiennguyen.amee/themes/ — the filename must match the theme’s own id.
  2. Set "active_theme": "<id>" in settings.json, in that same directory.
  3. 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.

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.

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.

Enforced on import — a theme that violates these is rejected with an error, not silently truncated:

  • id and name must be present and non-empty (after trimming whitespace).
  • tokens must 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.

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"
}
}
  • 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.