Writing a theme
my-theme/
theme.json tokens, modes, settings, overrides
components/ the components this theme replaces
schemaVersion: 2 is required. Version 1 manifests are rejected at upload
rather than half-supported.
A minimal theme
{
"schemaVersion": 2,
"id": "my-theme",
"name": "My Theme",
"modes": {
"default": "light",
"light": { "tokens": { "colors": { "background": "#ffffff", "foreground": "#16161a" } } },
"dark": { "tokens": { "colors": { "background": "#0e0e11", "foreground": "#f2f2f5" } } }
}
}
That is a working theme. Everything it does not declare keeps the platform's own value, so a theme is a list of the decisions you wanted to make differently rather than a full stylesheet.
Letting the operator change things
Declare the token as a field and the customiser draws it:
{
"tokens": {
"colors": {
"accent": { "label": "Accent", "default": "#1b34f5" }
},
"fonts": {
"display": { "label": "Headings", "default": "Archivo" }
}
}
}
Settings
Settings are content rather than colour: a tagline, a cover, an invite link.
Core renders the group at /admin/theme/<group> with labels, inputs, save and
reset.
{
"settings": {
"hero": {
"label": "Hero",
"fields": {
"tagline": { "type": "text", "label": "Tagline" },
"cover": { "type": "image", "label": "Cover image" },
"discord": { "type": "url", "label": "Discord invite" }
}
}
}
}
Replacing a component
{
"components": {
"Footer": "components/Footer.tsx",
"Hero": "components/Hero.tsx"
}
}
The component receives the same props the platform's own version receives. Read the theme's settings through the SDK rather than reaching for the database: a theme runs in the same bundle a visitor downloads.
Opening a slot
{
"slots": [{ "name": "hero.liveStats" }],
"slotContents": [{ "slot": "footer.top", "component": "components/Ribbon.tsx" }]
}
slots are places you open for modules. slotContents are things you put into
slots core or a module opened.
Suggesting modules
{
"suggestedModules": ["store", "servers"]
}
A banner the operator can dismiss. Nothing is installed and nothing is enabled.
Its own strings
{
"translations": {
"en": { "myTheme": { "joinUs": "Join us" } },
"tr": { "myTheme": { "joinUs": "Bize katıl" } }
}
}
Synced when the theme is activated, in every locale the platform ships.