Skip to content

Theme anatomy

ImAsra edited this page Jul 29, 2026 · 3 revisions

Theme Anatomy

Every theme lives in its own folder under themes/<id>/. The id must be lowercase kebab-case and must match the folder name exactly — it's also used as the [data-theme='<id>'] CSS selector and the CSS namespace for animations.

themes/<id>/
├── manifest.json
├── theme.css
├── thumbnail.png     (or .jpg)
└── assets/            (optional)

manifest.json

Field Required Notes
id yes Lowercase kebab-case, matches the folder name
name yes Display name in the Theme Store. Keep it short; a trademark-safe/altered name is fine if inspired by a brand, film, or game
author yes Your GitHub handle
version yes Plain X.Y.Z, no pre-release suffix. Must be bumped on every change on a already-published theme
description yes The store's search anchor — see Making-a-Theme#naming--description
mode yes "dark" or "light" — must match the color-scheme
minAppVersion optional set to 1.51.0 or later, Required if the theme uses assets/, or otherwise depends on a newer app feature
changelog optional Object keyed by version, e.g. { "1.0.1": ["Fixed X", "Tweaked Y"] } — shown in the store's What's new

Example (from template/manifest.json):

{
  "id": "template",
  "name": "Template",
  "author": "your-github-handle",
  "version": "1.0.0",
  "description": "Starting point for a community theme. Copy this folder, rename, recolour.",
  "mode": "dark",
  "tags": ["template"],
  "minAppVersion": "1.51.0", /* only add when using assets */
  "changelog": {
    "1.0.0": ["Initial release."]
  }
}

theme.css

Free-form CSS scoped under [data-theme='<id>']. The recommended starting point is recolouring the semantic tokens documented in Design-Tokens — that alone recolours the whole app — but you're not limited to them: any selectors, structure, @media, and @keyframes are fair game.

Themes can also react to live app state via attributes on that same root element:

[data-theme='<id>'][data-playing='true'] { /* ... */ }

Available state attributes: data-playing, data-fullscreen, data-sidebar-collapsed, data-lyrics-open.

color-scheme must be declared directly on the theme root (not as a custom property) and must match manifest.mode — it drives OS-level form/scrollbar theming and the store's dark/light filter.

thumbnail.png / .jpg

A 16:9 screenshot of Psysonic with your theme applied, at least 1280×720 (aspect ratio 1.5–1.85, source file ≤ 6 MB). You don't need to resize or convert it — CI optimises it into a thumbnail.webp automatically on merge.

No screenshot yet? Generate a quick placeholder:

node scripts/make-thumbnail.mjs themes/<your-id>/thumbnail.png "#15171e" 1280 720

assets/ (optional)

For images or fonts you'd rather ship as files than inline as data: URIs. See Local-Assets for the rules.

registry.json

Not part of a theme folder — this is the single auto-generated index the app actually reads over the CDN. It's regenerated from every theme's manifest.json on merge to main. Never edit it by hand. See Registry & Versioning.

Clone this wiki locally