⚠️ Design system CSS not found. Check the path in cms/docs.config.js → designSystemPath, then re-run npm run docgen.

Brand Setup

Setting up a new brand environment

Docs / Brand Setup
Download .md file
Open .md in new tab
On this page
  • What a Brand's Pages Carry
  • Quick Checklist
  • Step 1: Create the Brand Folder
  • Step 2: Fill in the Manifest
  • Step 3: Add Brand Logos
  • Step 4: Customise the Theme
  • Step 5: Build and Check
  • Creating from Template: Full Walkthrough
  • Architecture Reference

This guide covers how to set up a new brand environment from scratch: the manifest, the theme tokens, the logos, and what the generator does with them.

On logins. A brand instance used to be something a user logged into — auth resolved their brand and the theme followed. Auth was removed in August 2026 and the site is public but unlisted, so a brand's pages are built with its theme at generation time instead. The per-user half of this guide is on hold rather than deleted; it returns with whatever gating comes back. See ROADMAP.md → Auth.

Scope. This walkthrough is for brand instances hosted inside BrandOS — auth-driven theming on the docs site. A standalone consumer site (like the studio site) doesn't use brand instances: it loads one theme.css of its own after the engine CSS. See Setup for that model.

A brand instance is two files plus assets:

File Holds Owner
cms/brands/<brand>/brand.json Admin settings: name, description, Google Fonts URL, footer text, optional favicon and OG image paths Set at onboarding, rarely touched
cms/brands/<brand>/assets/theme.css Designer settings: font slots and roles, palette, semantic colours, dark mode Iterated during design

The generator reads every manifest and builds assets/js/theme-config.js from them on each build. That file is generated output. Never edit it by hand.

By Default itself runs on the same model: instance 0's manifest and theme live at cms/brands/bydefault/, and the engine (design-system.css) ships neutral defaults.


What a Brand's Pages Carry

A brand's generated pages differ from the root site in these ways:

Element Root site Brand pages
Logo By Default Brand logo from brand-name/assets/
Home link index.html brand-name/index.html
Theme By Default (instance 0's theme.css) Brand theme.css
Fonts Zalando Sans + ES Face (instance 0's theme.css) Per the brand's theme.css and brand.json font sources

All of it is decided at build time from the brand's folder — the generator emits each brand's chrome into its own pages.


Quick Checklist

  1. Copy cms/brands/brand-template/ to cms/brands/your-brand/
  2. Edit cms/brands/your-brand/brand.json: name, description, fonts URL, footer
  3. Update cms/brands/your-brand/_defaults.md with access: "brand:your-brand"
  4. Replace the demo logos in cms/brands/your-brand/assets/ with the brand's
  5. Customise cms/brands/your-brand/assets/theme.css
  6. Grant tool access: add your-brand to actionAccess in relevant cms/*.md files
  7. Run cd cms/generator && npm run docgen and check the brand's pages

There is no separate registration step. A folder with a brand.json and an assets/theme.css is a live brand: the generator adds it to the runtime registry and the build.


Step 1: Create the Brand Folder

Copy the cms/brands/brand-template/ folder and rename it to the brand name. Use lowercase, hyphenated names.

cp -r cms/brands/brand-template/ cms/brands/your-brand/

The template ships dressed as a demo brand (Brand Template), so every file you copy is a working example to replace, not an empty scaffold. The folder contains:

cms/brands/your-brand/
  brand.json      : Admin settings (the manifest)
  _defaults.md    : Access control defaults
  assets/
    theme.css     : Designer token overrides
    logo.svg      : Brand header logo (desktop)
    logo-small.svg : Compact logo variant (mobile)
    logos/        : Brand book logo set (light/dark variants)
  welcome.md      : Welcome page (optional)
  brand-book.md   : Brand book intro copy (optional)

On build, the generator copies assets/ to the output, generates the brand's pages, and builds the brand book from the logos/ folder. Nothing is hand-copied.

Set up access defaults

Update cms/brands/your-brand/_defaults.md:

---
access: "brand:your-brand"
---

This ensures all pages generated for this brand are scoped to their access level.


Step 2: Fill in the Manifest

Edit cms/brands/your-brand/brand.json:

{
  "name": "Your Brand",
  "description": "One line shown on the brand's home page.",
  "googleFontsUrl": "https://fonts.googleapis.com/css2?family=YourFont:wght@400..700&display=swap",
  "footerText": "© 2026 Your Brand"
}
Key Required Description
name Yes Display name: the switcher label, page titles, and the brand home hero
description No Hero copy on the brand's index page
googleFontsUrl No Google Fonts URL, injected on brand pages and at runtime. Use null for self-hosted fonts
typekitId No Adobe Typekit kit ID (e.g. wgr3lwl). Emits the kit stylesheet link into the brand's page heads
fontPreload No Array of site-relative font file paths to preload (prevents FOUC on the primary face)
footerText No Brand footer. Falls back to the site footer when omitted
faviconSvg / faviconIco No Absolute site paths to a brand favicon. Fall back to the site set
ogImage No Absolute site path to the brand's share image. Falls back to the site default

Font sources are per brand: a page head only carries the Typekit kit, preloads, and Google Fonts its own manifest declares. Self-hosted fonts pair a fontPreload entry with @font-face declarations in the brand's theme.css (see Step 4); the runtime preview switcher loads Google Fonts only, so Typekit-served faces show on the brand's own pages, not in a preview from another space.

The folder name is the brand key: it names the manifest, the theme, the logo path and the brand's output folder.

Note: Tool access is not configured here. It is controlled via actionAccess in cms/*.md frontmatter, which decides whether a tool's action button appears on a brand's pages.


Step 3: Add Brand Logos

Logo files live in the brand source folder with fixed names:

cms/brands/your-brand/assets/logo.svg        : Full header logo (desktop)
cms/brands/your-brand/assets/logo-small.svg  : Compact variant (mobile, future use)
cms/brands/your-brand/assets/logos/          : Brand book set, logo_brand-<type>-<light|dark>.svg

The generator copies these to your-brand/assets/ on build and builds the brand book page from the logos/ set.

Logo requirements

  • Format: SVG
  • Sizing: The container is 50px tall (desktop) / 45px tall (mobile). Width scales automatically. A wide aspect ratio (roughly 2:1) works best.
  • Colour: Use fixed colours, or currentColor if you want the logo to adapt to the theme's text colour and dark mode.
  • Cleanup: Run logos through the SVG Cleaner before adding them:
npx svg-cleaner --strip-comments --size -o cms/brands/your-brand/assets/logo.svg <<'SVGEOF'
<svg>...paste logo SVG here...</svg>
SVGEOF

How it works

When a brand logs in, theme-loader.js fetches the logo from your-brand/assets/logo.svg and injects it into the header. When an admin previews a theme, the logo swaps too. When the theme is unloaded, the By Default logo is restored.


Step 4: Customise the Theme

Open cms/brands/your-brand/assets/theme.css and update the token values. The copied file is the Brand Template demo theme, a complete working example of the override surface.

What to override

Category Tokens Purpose
Typography … , plus the roles , , , Brand fonts. Add @font-face in the theme file if self-hosting
Neutral scale through The grey ramp behind surfaces and text
Text , , , , Core text colours
Backgrounds , Surface colours
Borders , , Border and input outline colours
Accent , , , --selection-* Accent colour across UI

Buttons are not a theme surface: they derive from --text-primary and --text-inverted at component scope and re-skin through those tokens.

Tokens you usually don't need to override

  • --text-faded, --background-faded, --border-faded: alpha-based, work across any theme
  • --status-*-bg: in light mode, tints of the accent primitives; in dark mode, derived from the page ground pulled toward the status colour
  • Spacing, radius, type scale, breakpoints: engine rhythm, not brand expression

Dark mode

Theme the [data-theme="dark"] block, then mirror it exactly inside the @media (prefers-color-scheme: dark) fallback for users without JavaScript. Drift between the two blocks is a known failure mode; keep them identical.

The fallback must stay scoped to :root:not([data-theme]) — it applies only while no explicit choice is set. Any data-theme value, including "light", defeats it; that is what lets a visitor's stored light choice win over a dark OS. Toggles must always set an explicit "light" or "dark" value, never remove the attribute (see the Dark Mode section in the Colour doc).

How it works

Brand pages link the theme statically after the design system CSS; at runtime theme-loader.js injects the same file when a brand user logs in or an admin previews the brand. Later token values win the cascade, so the theme overrides the engine's neutral defaults and the whole site follows.


Step 5: Build and Check

cd cms/generator && npm run docgen

Open the brand's own pages and confirm: the logo is the brand's, the home link points at your-brand/index.html, the theme's colours and faces have taken, and dark mode holds.

The folder name is the brand key, and it is the single identifier tying everything together:

  • Manifest: cms/brands/your-brand/brand.json
  • Theme CSS: your-brand/assets/theme.css
  • Logo: your-brand/assets/logo.svg
  • Home link: your-brand/index.html

Parked: per-user brand resolution

Until August 2026 a brand user logged in, and their brandFolder role tag decided which theme, logo and home link they got — plus a Theme Preview switcher for admins and a dev bar for testing roles locally. That machinery is dormant, not deleted: theme-loader.js still loads and unloads themes, but nothing calls it. If gating returns, this is the step that comes back with it. See ROADMAP.md → Auth.


Creating from Template: Full Walkthrough

Here is the complete workflow for adding a brand, start to finish:

# 1. Copy the brand source template
cp -r cms/brands/brand-template/ cms/brands/your-brand/

# 2. Clean and add logos
npx svg-cleaner --strip-comments --size \
  -o cms/brands/your-brand/assets/logo.svg <<'SVGEOF'
<svg>...paste full logo...</svg>
SVGEOF

npx svg-cleaner --strip-comments --size \
  -o cms/brands/your-brand/assets/logo-small.svg <<'SVGEOF'
<svg>...paste small logo...</svg>
SVGEOF

Then manually:

  1. Edit cms/brands/your-brand/brand.json: name, description, font sources, footer
  2. Edit cms/brands/your-brand/assets/theme.css: replace the demo values with the brand's fonts and colours
  3. Replace the demo SVGs in cms/brands/your-brand/assets/logos/ with the brand's light/dark set
  4. Update cms/brands/your-brand/_defaults.md:
    ---
    access: "brand:your-brand"
    ---
    
  5. Edit cms/brands/your-brand/welcome.md and brand-book.md if needed
  6. Grant tool access by adding the brand to actionAccess in the relevant tool markdown, e.g. in cms/display-ad-preview-docs.md change actionAccess: "brand:brand-template" to actionAccess: "brand:brand-template,your-brand"
  7. Run cd cms/generator && npm run docgen
  8. Open the brand's pages and check the logo, home link, theme and dark mode

Architecture Reference

File Purpose
cms/brands/<brand>/brand.json The manifest: admin settings. With assets/theme.css alongside it, the brand is live
cms/brands/<brand>/assets/theme.css Designer token overrides
assets/js/theme-config.js GENERATED runtime registry, built from the manifests on every docgen. Never hand-edit
assets/js/theme-loader.js Loads/unloads theme CSS, fonts, logo, and home link dynamically. Dormant — nothing calls it since auth was removed
scripts/theme-regression/check.py Instance-0 token regression check: run after editing the engine's tokens or By Default's theme

Instance 0 is the exception that proves the model: cms/brands/bydefault/ holds By Default's manifest and theme, and the root site loads that theme as a static link on every page. By Default gets its identity the same way a client brand does, which is what keeps the engine neutral.

Data flow

Build
  → generator reads every cms/brands/*/brand.json
  → brands with assets/theme.css enter the runtime registry
  → theme-config.js is emitted in one write
  → brand pages are generated with the brand's chrome
      · theme.css linked last, after design-system.css and docs-site.css
      · logo, home link, title, footer, favicon and OG image from the manifest

There is no second, runtime half to this flow today. Everything a brand's pages
carry is decided here, at build time.

On this page
  • What a Brand's Pages Carry
  • Quick Checklist
  • Step 1: Create the Brand Folder
  • Step 2: Fill in the Manifest
  • Step 3: Add Brand Logos
  • Step 4: Customise the Theme
  • Step 5: Build and Check
  • Creating from Template: Full Walkthrough
  • Architecture Reference
Previous Folders
Next Changelog

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback

Send feedback

© 2026 By Default