Summit of SummitsReturn to the Source
Every theme ships a theme.json at its root declaring metadata,
the layouts it's compatible with, design tokens, and the list of
files shipped. The manager UI validates this manifest on upload;
the processor reads config to generate theme_css.
For local themes:
public_html/lazysite/layouts/LAYOUT/themes/THEME/theme.json
For remote layouts (which bundle a theme in the same URL directory):
https://example.com/layouts/clean/theme.json
{
"name": "odcc",
"version": "1.0.0",
"description": "OpenDigitalCC brand theme",
"author": "OpenDigitalCC",
"layouts": ["default"],
"config": {
"colours": {
"primary": "#332b82",
"text": "#2a2a2a",
"accent": "#ff6b35"
},
"fonts": {
"body": "Open Sans",
"heading": "Source Sans 3"
},
"spacing": {
"unit": "8px"
}
},
"files": [
"theme.json",
"main.css",
"assets/logo.svg"
]
}
name - matches the directory name. Sanitised to
[A-Za-z0-9_-] on read.version - semver string.description - free-text, human-readable.author - free-text.layouts - array of layout names this theme is compatible
with. The manager rejects an upload whose layouts field is
missing or empty (DP-C, strict). The processor refuses to
apply a theme to any layout not in this list.config - object grouping design tokens. Group names are
author-chosen (common: colours, fonts, spacing, icons).
Keys within groups are author-chosen. Values must be strings.files - array of paths shipped with the theme. Used by
remote-layout auto-fetch (downloads each listed file alongside
the layout). Not consulted for local themes on upload; the zip
contents dictate what lands on disk.The processor walks config and emits a <style>:root { ... }
block exposed as [% theme_css %]. Naming convention:
--theme-GROUP-KEY
So config.colours.primary = "#332b82" becomes
--theme-colours-primary: #332b82. Values are emitted verbatim
except ;{}<> characters which are stripped to prevent
declaration escape.
[% theme_assets %] is set to:
/lazysite-assets/LAYOUT/THEME//lazysite-assets/CACHE_KEY/| Condition | Result |
|---|---|
name missing or unsanitisable |
Install rejected |
layouts missing or empty |
Install rejected |
| Declared layout not installed | Install rejected |
Active layout not in layouts[] |
Theme ignored at render |
config not an object |
theme_css empty |
| Nested group value (not a scalar) | Value skipped silently |
files[] paths containing .. are skipped (traversal
protection).files[] are created automatically when
fetching remote assets.