Skip to content
Documentation

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.