Skip to main content

Configuration

Brand assets

Pluggable brand manifest — logos, favicons, OG images, PWA manifest — declared once, resolved everywhere.

config.brand centralises every brand-sized asset in one declarative object. The platform reads this once per render and wires up favicons, OG image, Apple touch icon, PWA manifest, and the logos you reference from colors.logo — no per-feature configuration scattered across the config file.

The four icons most sites need — favicon, dark favicon, Apple touch icon and PWA icon — are editable under Configurations → Branding, beside the logo fields. The rest of the block (colours, typography, asset families) is JSON-only and is preserved untouched when you save from the panel.

Only root, name, and favicon.dir are required. Every other field is written when you have something to put in it — an imported site typically has a .ico, an apple-touch tile and two or three PNGs, and no SVG masters or maskable variants at all.

Minimal example

Three fields carry the paths; everything else is optional and written only when you have the file:

{
  "brand": {
    "root": "/brand",
    "name": "acme",
    "favicon": {
      "dir": "favicon",
      "ico": "favicon.ico",
      "png": { "32": "favicon-32x32.png" }
    }
  }
}

Files resolve as ${root}/${favicon.dir}/${file} — the block above points at /brand/favicon/favicon.ico. Declare only the icons you actually ship: a site with a .ico and one PNG writes those two and leaves the SVG entries out.

A complete block

{
  "brand": {
    "root": "/brand-assets",
    "name": "acme",
    "shortName": "Acme",
    "description": "Acme documentation",
    "colors": {
      "brand": { "light": "#3b82f6", "dark": "#60a5fa" },
      "text":  { "light": "#0f172a", "dark": "#f8fafc" },
      "background": { "light": "#ffffff", "dark": "#0f172a" }
    },
    "themeColor": { "light": "#ffffff", "dark": "#0f172a" },
    "backgroundColor": { "light": "#ffffff", "dark": "#0f172a" },
    "typography": {
      "family": "Inter",
      "weight": 500,
      "fallback": "system-ui, -apple-system, sans-serif"
    },
    "assets": {
      "wordmark": {
        "dir": "wordmark",
        "file": "acme-wordmark",
        "hasSvg": true,
        "hasTheme": true,
        "pngSizes": ["h40", "h80", "h120", "h240"]
      },
      "icon": {
        "dir": "icon",
        "file": "acme-icon",
        "hasSvg": true,
        "hasTheme": false,
        "pngSizes": [32, 48, 64, 128, 256, 512]
      },
      "maskable": {
        "dir": "maskable",
        "file": "acme-maskable",
        "hasSvg": false,
        "hasTheme": false,
        "pngSizes": [192, 512, 1024]
      },
      "og": {
        "dir": "og",
        "file": "acme-og",
        "hasSvg": false,
        "hasTheme": true,
        "pngSizes": ["1200x630"],
        "dimensions": { "width": 1200, "height": 630 }
      }
    },
    "favicon": {
      "dir": "favicon",
      "ico": "favicon.ico",
      "svg": "favicon.svg",
      "svgLight": "favicon-light.svg",
      "svgDark": "favicon-dark.svg",
      "apple": "apple-touch-icon.png",
      "maskable512": "maskable-512.png",
      "png": { "16": "favicon-16.png", "32": "favicon-32.png", "48": "favicon-48.png" },
      "androidChrome": { "192": "android-chrome-192.png", "512": "android-chrome-512.png" }
    },
    "manifest": "/manifest.webmanifest"
  }
}

Fields

brand.rootstringrequired

Absolute URL prefix for every asset. All assets.*.dir paths resolve under this prefix. Example: "/brand-assets" means the wordmark SVG lives at /brand-assets/wordmark/acme-wordmark.svg.

brand.namestringrequired

Slug-form brand name. Used in generated filenames + metadata.

brand.shortNamestring

PWA short name (shown on Android / iOS home screens when the docs site is installed).

brand.descriptionstring

PWA manifest description + generic fallback description when seo.description is absent.

brand.colorsBrandColorPair

Themed colours used by the platform for generated assets:

  • brand.light / brand.dark — the primary accent pair (same values you'd set in colors.light / colors.dark, duplicated here so brand pipelines have a standalone source).

  • text.light / text.dark — default body text colour for generated OG images.

  • background.light / background.dark — page background colour for OG images.

brand.themeColor{ light: string; dark: string }

Browser chrome colour (<meta name="theme-color">). Controls the mobile Safari/Chrome top bar colour per light/dark mode.

brand.backgroundColor{ light: string; dark: string }

PWA manifest background_color. Shown while the docs site is loading in a PWA shell.

brand.typographyBrandTypography

Default site typography. family + weight + fallback are emitted as the base font-family CSS variable.

brand.assetsBrandAssetPattern map

Four required asset families, each described by a BrandAssetPattern:

  • wordmark — full logo with text. Sizes typed as height (h40 → 40px tall).

  • icon — square app icon. Sizes typed as pixel dimensions (32 → 32×32).

  • maskable — PWA maskable icon (safe-area padding included). Sizes 192, 512, 1024.

  • og — Open Graph preview image. Must carry dimensions: { width, height } (usually 1200×630).

Each pattern:

  • dir — subfolder under brand.root.

  • file — filename stem (no extension, no size suffix).

  • hasSvg — set true if an SVG version is present under {root}/{dir}/{file}.svg.

  • hasTheme — set true if you ship separate light/dark SVG variants ({file}-light.svg, {file}-dark.svg).

  • pngSizes — array of PNG sizes you've generated; the renderer links to each as {file}-{size}.png.

brand.faviconBrandFaviconSet

The block is optional; when present, dir is required — it is the directory every filename below resolves against. Each entry names a file you ship, and the ones you do not ship are simply left out.

Favicons follow a standard layout:

  • ico — classic favicon.ico (16x16, 32x32, 48x48 multi-size).

  • svg — adaptive SVG favicon (uses @media (prefers-color-scheme) internally).

  • svgLight / svgDark — explicit per-scheme SVG for browsers that want a strict media query.

  • png{16, 32, 48} PNGs for legacy browsers.

  • appleapple-touch-icon.png (180x180).

  • androidChrome{192, 512} PNGs for the PWA.

  • maskable512 — maskable 512x512 PNG (separate from the regular 512 because of safe-area padding).

brand.manifeststring

Absolute path to the .webmanifest file. Usually /manifest.webmanifest at your repo root.

Building the asset set — exact files, sizes, folder

Everything lives under {brand.root}/ in your repo's public/ folder, in five subfolders. This is exactly how NookDocs ships its own brand (the live reference). With "root": "/brand-assets" and the name-stemmed example acme:

public/brand-assets/
├── manifest/
│   └── manifest.json              ← brand.manifest → "/brand-assets/manifest/manifest.json"
├── favicon/                       ← brand.favicon  — RENDERED on every page (tab + PWA icons)
│   ├── favicon.svg                ← adaptive (light+dark via inline @media)
│   ├── favicon-light.svg          ← explicit light
│   ├── favicon-dark.svg           ← explicit dark
│   ├── favicon.ico                ← 16+32+48 multi-res
│   ├── favicon-16x16.png
│   ├── favicon-32x32.png
│   ├── favicon-48x48.png
│   ├── apple-touch-icon.png       ← 180×180  (iOS home screen)
│   ├── android-chrome-192x192.png ← 192×192  (Android / PWA)
│   ├── android-chrome-512x512.png ← 512×512  (PWA splash)
│   └── maskable-icon-512.png      ← 512×512  (safe-area padding)
├── icon/                          ← brand.assets.icon — square app icon, themed
│   ├── acme-icon-light.svg
│   ├── acme-icon-dark.svg
│   ├── acme-icon-light-16.png … acme-icon-light-1024.png
│   └── acme-icon-dark-16.png  … acme-icon-dark-1024.png
├── maskable/                      ← brand.assets.maskable — padded PWA icon, themed
│   ├── acme-maskable-light.svg / acme-maskable-dark.svg
│   └── acme-maskable-{light,dark}-{192,512,1024}.png
├── og/                            ← brand.assets.og — social preview, themed
│   ├── acme-og-light.svg / acme-og-dark.svg
│   └── acme-og-{light,dark}-1200x630.png
└── wordmark/                      ← brand.assets.wordmark — full logo, themed (by height)
    ├── acme-wordmark-light.svg / acme-wordmark-dark.svg
    └── acme-wordmark-{light,dark}-{h40,h80,h120,h240,h480}.png

Naming convention

  • Favicon dir uses fixed, standard names (the values you put in brand.favicon). These are exactly what realfavicongenerator.net produces.

  • Themed families (icon / maskable / og / wordmark) follow {file}-{theme}.svg and {file}-{theme}-{size}.png, where theme is light / dark (because hasTheme: true) and size is each entry in that family's pngSizes. Icon/maskable sizes are pixels; wordmark sizes are heights (h40 = 40px tall); OG is 1200x630.

What renders where

FamilyRendered on tenant <head>?SizesLight/Dark
favicon + manifestYes — tab icon, iOS icon, PWA installico, 16/32/48, 180, 192, 512, maskable 512SVG light/dark; PNGs single
iconApp/PWA icon sources16, 32, 48, 64, 96, 128, 180, 192, 256, 384, 512, 1024both
maskableMaskable PWA icon192, 512, 1024both
ogSocial preview1200×630both
wordmarkLogo in richer layoutsh40, h80, h120, h240, h480both

Only the favicon set + manifest are emitted into the page <head> for a tenant site. The themed icon / maskable / og / wordmark families round out a complete brand package (and feed richer layouts), but your visible navbar logo comes from colors.logo and your social preview can come from seo.ogImage. Ship the favicon set for the essentials; add the rest for a full brand.

Generate from masters

Provide a light and a dark master SVG per family (square for favicon/icon/maskable; wide for wordmark; 1200×630 for og), then scale with rsvg-convert + imagemagick:

ROOT=public/brand-assets

# Favicon (single-variant PNGs; SVGs are light/dark) — from a square master
for s in 16x16 32x32 48x48; do rsvg-convert -w ${s%x*} -h ${s#*x} favicon.svg > $ROOT/favicon/favicon-$s.png; done
rsvg-convert -w 180 -h 180 favicon.svg > $ROOT/favicon/apple-touch-icon.png
rsvg-convert -w 192 -h 192 favicon.svg > $ROOT/favicon/android-chrome-192x192.png
rsvg-convert -w 512 -h 512 favicon.svg > $ROOT/favicon/android-chrome-512x512.png
rsvg-convert -w 512 -h 512 favicon-maskable.svg > $ROOT/favicon/maskable-icon-512.png
convert $ROOT/favicon/favicon-16x16.png $ROOT/favicon/favicon-32x32.png $ROOT/favicon/favicon-48x48.png $ROOT/favicon/favicon.ico

# Themed families — repeat per theme + size (icon shown; same loop for maskable/og/wordmark)
for t in light dark; do
  for s in 16 32 48 64 96 128 180 192 256 384 512 1024; do
    rsvg-convert -w $s -h $s acme-icon-$t.svg > $ROOT/icon/acme-icon-$t-$s.png
  done
done

Manifest

brand.manifest points at the .webmanifest you ship:

{
  "name": "Acme Docs",
  "short_name": "Acme",
  "icons": [
    { "src": "/brand-assets/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/brand-assets/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/brand-assets/favicon/maskable-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ],
  "theme_color": "#0f172a",
  "background_color": "#0f172a",
  "display": "standalone"
}

No brand block? Leave it out and your site uses seo.favicon (a single SVG) for the tab icon — nothing breaks. The brand block is the upgrade path to the full favicon + PWA + maskable + OG set. The starter template ships without a brand block on purpose — so add this folder + block only when you're ready.

Why this is a block, not individual keys

Older config formats scattered brand metadata across colors.logo.light, seo.ogImage, seo.favicon, theme.colors, PWA manifest fields, and Apple touch icon. Every feature had to reach into a different key to find the right asset, and cross-feature consistency was manual. The brand block is one source of truth — change the primary colour in one place and favicons, OG images, and themed wordmarks all track it.

The root-level colors.logo.light / colors.logo.dark are still accepted (backwards compat for projects that haven't adopted brand), but brand.assets.wordmark is the authoritative path when both are set.

Was this page helpful?

Last updated August 20, 2026