> For the complete documentation index, see [llms.txt](https://docs.kouch.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kouch.dev/docs/theme_format.md).

# Theme format — kouch-theme/2

A Kouch theme is a **folder of data**: one `theme.json` plus the images, sounds, music, fonts and SVGs it names. There is no CSS text and no script. Kouch validates everything and turns it into CSS custom properties and file paths itself (ADR 0008). The built-in theme stays exactly as `docs/DESIGN.md` defines it; a theme can only change what this document lists.

Where themes live:

| Source   | Folder                                          | Id                   |
| -------- | ----------------------------------------------- | -------------------- |
| Local    | `%APPDATA%\kouch\themes\<folder>\theme.json`    | `local:<folder>`     |
| Workshop | a subscribed item whose folder has `theme.json` | `workshop:<item id>` |
| Built-in | —                                               | `default`            |

A complete sample lives in [`examples/themes/night-sky`](https://github.com/golfista/kouch/tree/main/examples/themes/night-sky/README.md); a test keeps it valid.

Pick a theme in **Settings › Theme**. The choice is stored as `settings.display.theme`. A theme that fails validation is still listed, greyed out with the reason. A theme that goes missing or breaks later falls back to the built-in one.

Folder names for local themes: letters, digits, `-`, `_`, `.` and spaces; at most 64 characters; not starting with `.`.

## `theme.json`

```json
{
  "$schema": "kouch-theme/2",
  "kouch_theme": 2,
  "name": "Aurora",
  "author": "Someone",
  "version": "1.0.0",
  "description": "Deep blue night with soft glass cards.",
  "preview": "preview.webp",
  "schemes": ["black", "white"],

  "tokens": {
    "--k-radius-tile": "0.75rem",
    "--k-ease-spring": "cubic-bezier(0.3, 1.25, 0.6, 1)"
  },
  "black": {
    "tokens": {
      "--k-accent": "#8AB4FF",
      "--k-bg": "#05060F",
      "--k-surface-1": "#0E1224",
      "--k-bg-gradient": "linear-gradient(180deg, #101A40, #05060F 70%)",
      "--k-card-shadow": "0px 8px 24px #00000099"
    }
  },
  "white": {
    "tokens": { "--k-accent": "#1D4ED8" }
  },

  "library_view": "carousel",
  "card_shape": "portrait",
  "background": {
    "image": "backgrounds/night.webm",
    "opacity": 0.35,
    "systems": { "arcade": "backgrounds/arcade.webp" }
  },
  "ambient": { "enabled": true, "density": 0.4, "opacity": 0.05 },
  "sounds": { "move": "sounds/move.ogg", "accept": "sounds/accept.ogg", "boot": "sounds/boot.ogg" },
  "music": "music/menu-loop.ogg",
  "fonts": {
    "body": { "family": "Some Sans", "file": "fonts/some-sans.woff2" },
    "display": { "family": "Some Display", "file": "fonts/some-display.woff2" }
  },
  "silhouettes": { "pad-standard": "devices/pad.svg" },
  "device_imagery": false
}
```

Only `kouch_theme` (must be `2`) and `name` are required. Keys starting with `$` are ignored. **Any other unknown key is an error**, so a `css` or `script` key fails loudly rather than being quietly skipped.

| Key                                | Meaning                                                                                                                                                                                                                                                                                              |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                             | Up to 48 characters of plain text.                                                                                                                                                                                                                                                                   |
| `author`, `version`, `description` | Optional plain text: 48, 24 and 280 characters.                                                                                                                                                                                                                                                      |
| `preview`                          | An image for the theme picker.                                                                                                                                                                                                                                                                       |
| `schemes`                          | Which of the built-in Black/White schemes the theme styles. Default is both.                                                                                                                                                                                                                         |
| `tokens`                           | Tokens applied in both schemes.                                                                                                                                                                                                                                                                      |
| `black.tokens`, `white.tokens`     | Per-scheme tokens; they override `tokens`.                                                                                                                                                                                                                                                           |
| `library_view`                     | Suggested Library view: `grid`, `classic`, `carousel` or `list`. The user's own choice still wins once they pick one. **Ignored from the Home/Library redesign on** (the Library's views are Grid A–Z, Shelf and Playtime; docs/HOME\_LIBRARY\_PLAN.md §4); still accepted so older themes validate. |
| `card_shape`                       | `portrait`, `square` or `landscape`.                                                                                                                                                                                                                                                                 |
| `background.image`                 | Image or looping video behind every screen.                                                                                                                                                                                                                                                          |
| `background.opacity`               | 0–0.6.                                                                                                                                                                                                                                                                                               |
| `background.systems`               | Per-system backgrounds, keyed by system id (`[a-z0-9_-]`, up to 32 characters).                                                                                                                                                                                                                      |
| `ambient`                          | The floating-shapes background: `enabled`, `density` 0–1 (default 0.5), `opacity` 0–0.2 (default 0.05). The user's own switch in Settings still wins.                                                                                                                                                |
| `sounds`                           | Any of the 13 slots: `move`, `accept`, `back`, `error`, `launch`, `page`, `sheet_open`, `sheet_close`, `pad_connected`, `pad_disconnected`, `player_joined`, `toast`, `boot`. Missing slots keep Kouch's sound.                                                                                      |
| `music`                            | Menu music loop. It plays only if the user has music turned on.                                                                                                                                                                                                                                      |
| `fonts.body`, `fonts.display`      | A bundled `.woff2`. `family` is only a label; Kouch names the face `kt-body` / `kt-display` itself.                                                                                                                                                                                                  |
| `silhouettes`                      | SVGs replacing Kouch's device silhouettes, keyed by name (see below).                                                                                                                                                                                                                                |
| `device_imagery`                   | Set `true` if the theme contains third-party console or controller imagery (ADR 0009).                                                                                                                                                                                                               |

## Tokens

The allowed tokens and their value types are in [`theme-token-allowlist.json`](https://github.com/golfista/kouch/tree/main/docs/theme-token-allowlist.json), the one list both Rust and the UI read. Every value is parsed and written back out by Kouch; nothing is passed through as text.

| Type       | Accepted form                                                                                          | Tokens                                                                                                                                         |
| ---------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `hex6`     | `#rrggbb`                                                                                              | `--k-accent`, `--k-bg`, `--k-text`, `--k-text-2`, `--k-text-3`                                                                                 |
| `color`    | `#rrggbb` or `#rrggbbaa`                                                                               | surfaces 1–4, `--k-text-disabled`, hairlines, `--k-outline`, `--k-scrim`, `--k-dim`                                                            |
| `rem`      | `0.75rem`, 0–1.5                                                                                       | the four radii                                                                                                                                 |
| `ms`       | `220ms`, 0–400 (`--k-dur-flip` up to 600)                                                              | durations                                                                                                                                      |
| `number`   | a plain number                                                                                         | `--k-bg-image-opacity`, `--k-hero-opacity`, `--k-game-hero-opacity` (0–0.6), `--k-tile-focus-scale` (1–1.15), `--k-tile-press-scale` (0.9–1.1) |
| `ease`     | `cubic-bezier(x1, y1, x2, y2)`, x 0–1, y −1–2                                                          | `--k-ease-out`, `--k-ease-in`, `--k-ease-spring`                                                                                               |
| `gradient` | `linear-gradient(<deg>deg, stops…)` or `radial-gradient(stops…)`, 2–6 stops of `<color> [<0–100>%]`    | `--k-bg-gradient`                                                                                                                              |
| `shadow`   | one or two comma-separated `<x>px <y>px <blur>px [<spread>px] <color>`, each length −64–64 (blur 0–64) | `--k-card-shadow`                                                                                                                              |

No `var()`, `url()`, `calc()`, keywords or named colors.

Rules checked on every load:

* **Accent.** `--k-accent` must pass DESIGN.md §2.3's gate for the scheme it applies to: at least 4.5:1 contrast on that scheme's page (black or white) and saturation `max(r,g,b) − min(r,g,b) ≥ 40`.
  * An accent set in `tokens` that fails on one scheme is dropped for that scheme only, with a warning in the log.
  * The chain is: the user's own accent > the theme's > the OS accent (gated) > Kouch's default.
* **Readable text.** In each scheme, `--k-text` and `--k-text-2` need at least 4.5:1 against `--k-bg`, and `--k-text-3` at least 3:1. The theme's values are used where it sets them, the built-in ones otherwise. A theme that fails this is refused.
* **Reduced motion.** When reduced motion is on, the durations and tile scales a theme sets are forced back to the reduced values.

## Files

Paths are relative to the theme folder and must stay inside it:

* no `..` components
* no absolute paths, drive letters or `:`
* no links that lead out of the folder
* no `system` or `bios` folder
* nothing remote (there are no URLs at all)

| Use         | Types                          | Size cap          |
| ----------- | ------------------------------ | ----------------- |
| `preview`   | png, jpg, webp, gif            | 8 MB              |
| backgrounds | png, jpg, webp, gif; mp4, webm | 8 MB; 64 MB video |
| sounds      | wav, ogg, mp3                  | 2 MB each         |
| music       | ogg, mp3                       | 16 MB             |
| fonts       | woff2                          | 2 MB              |
| silhouettes | svg                            | 256 KB            |

The `theme.json` file itself must be 256 KB or less.

**SVG** must be shapes only. Refused:

* `<script>`, `<foreignObject>`, `<image>`, `<iframe>`, `<embed>`, `<object>`
* `on…=` event attributes
* `<!DOCTYPE>` / `<!ENTITY>`
* `javascript:` / `data:` URLs, `@import`
* any `href` or `url()` that isn't a `#fragment` in the same file

The UI shows SVGs as images or CSS masks, never as inline markup. Draw silhouettes in one color (`currentColor` or black) so Kouch can tint them with the player color.

Silhouette names: `pad-standard`, `pad-touchpad`, `pad-trackpad`, `pad-trackpad-2`, `pad-classic`, `phone`, `wand-pair`, `handheld`, `pad-pro`, `pad-pair`, `pad-half-left`, `pad-half-right`, `keyboard-mouse`, `remote`, `lan`.

## Rules for what a theme may show

* Kouch itself never ships or names third-party consoles or controllers (ground rule 1).
* Workshop themes may include such imagery. They must set `device_imagery: true` and carry the Workshop tag Kouch lists for it. Kouch hides and ignores any item whose id is in `<config>/workshop-blocklist.json` (ADR 0009). A blocklist Kouch fetches, so reported items can be pulled for everyone, is planned but not built yet.
* Themes must not include game art for specific titles, ROMs, BIOS or firmware (ground rule 2).

## How Kouch applies a theme (for Kouch developers)

* **Validation.** `app/src-tauri/src/theme_pack.rs` validates a theme into a `Theme`. `theme.rs` turns it into `ThemeState`:
  * `css`: the accent, safe area and text scale, then `@font-face` rules for bundled fonts.
  * Black tokens go under `:root:not([data-scheme="white"])` and White tokens under `:root[data-scheme="white"]`, followed by the reduced-motion re-assertion.
  * `extras`: every non-token setting, with absolute paths.
* **Delivery.** The UI injects `css` into `#k-theme` (last in `<head>`) and loads every path through `kmedia`. `kmedia` serves fonts, sounds and SVG only from theme folders, and sends every response with a no-script CSP.
* **Commands.**
  * `theme_list`: summaries of every theme, including broken ones.
  * `theme_set { id }`: validates the theme fresh, persists it and emits `theme:changed`.
  * `theme_get`.
  * `theme_create { name }`: a new local theme folder to edit by hand, copied from the active *local* theme (never a Workshop one) or a minimal `theme.json` with the user's own accent.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kouch.dev/docs/theme_format.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
