Theme Skins

Theme skins let one Frontbacked theme ship multiple visual looks. A site owner can choose the skin they want, while the theme developer keeps the page structure, FQL state, FRL rules, and assets in one theme.

A skin is CSS. Use it for color systems, fonts, spacing, background images, and any presentation-only differences.

Basic Setup

Create a skins/ folder in the theme root:

my-theme/
  index.html
  signup.html
  assets/
  skins/
    midnight.default.light.css
    midnight.default.dark.css
    solar_flare.light.css
    solar_flare.dark.css
    terra_bloom.css

Then reference the active skin from each HTML page:

<link rel="stylesheet" href="$skin.css" data-frontbacked-skin>

When Frontbacked serves the page, it replaces $skin.css with the CSS file for the selected skin. If the active site skin is solar_flare and the selected mode is dark, the rendered page receives:

<link rel="stylesheet" href="/skins/solar_flare.dark.css" data-frontbacked-skin>

data-frontbacked-skin is recommended because it gives frontbacked.js a stable link element to update when the visitor switches modes.

Filename Inference

skins/manifest.json is optional. Without it, Frontbacked reads the skin list from CSS filenames.

FilenameMeaning
midnight.default.light.cssmidnight skin, default skin, light mode.
midnight.default.dark.cssmidnight skin, default skin, dark mode.
solar_flare.light.csssolar_flare skin, light mode.
solar_flare.dark.csssolar_flare skin, dark mode.
terra_bloom.cssterra_bloom skin with one stylesheet for every mode.
default.cssA one-file starter skin.

If no file has .default., Frontbacked chooses default.css when it exists. If there is no default.css, it chooses the first skin alphabetically.

Skin display names are generated from filenames. For example, solar_flare becomes Solar Flare.

Optional Manifest

Add skins/manifest.json when you want custom names, ordering, descriptions, previews, or a default mode:

{
  "default": "midnight",
  "defaultMode": "system,dark",
  "order": ["midnight", "solar_flare", "terra_bloom"],
  "skins": {
    "midnight": {
      "name": "Midnight",
      "description": "A quiet dark-first skin with a bright trading accent.",
      "defaultMode": "dark",
      "modes": ["light", "dark"],
      "preview": "/skins/previews/midnight.png"
    },
    "solar_flare": {
      "name": "Solar Flare",
      "modes": ["light", "dark"]
    },
    "terra_bloom": {
      "name": "Terra Bloom"
    }
  }
}

The manifest never has to repeat CSS file paths. The CSS filenames still define which files exist.

defaultMode can be a concrete mode such as light or dark, or a system preference:

{
  "defaultMode": "system,dark"
}

system uses the visitor device preference in the browser. system,dark means use the device preference when available and use dark as the server-rendered fallback.

Use hidden: true for a skin entry when you want to keep a CSS file in the repo without offering it to site owners yet.

Admin Selection

Skin management lives in the site admin panel (/fb-admin). The available skins are read from the resolved theme version's source map, so each version can have a different skin list.

Frontbacked stores the selected skin by commit/version:

{
  "theme_skins": {
    "commit_123": {
      "skin": "solar_flare"
    }
  }
}

Published sites and the local frontbacked-server both use the same resolver:

  1. URL skin override: ?skin=solar_flare
  2. Version skin setting: settings.theme_skins[commitId].skin
  3. skins/manifest.json skin default
  4. Filename-inferred skin default

If a newer version has no skin selected yet, Frontbacked can fall back to the last selected skin name when that skin exists in the newer version. Mode is separate from site settings. The active mode comes from the visitor's URL/cookie preference first, then from defaultMode in skins/manifest.json, then from filename inference.

Runtime Switching

frontbacked.js exposes skin helpers:

Frontbacked.setSkinMode("dark");
Frontbacked.setSkinMode("system,dark");
Frontbacked.toggleSkinMode();

toggleSkinMode() switches between light and dark when both exist. For custom modes, it cycles through the modes listed for the skin.

The runtime updates the data-frontbacked-skin link and saves only the visitor's mode preference. Site owners choose the skin from fb-admin.

Frontbacked does not expose public setSkin or toggleSkin methods to themes. A theme should not let visitors choose a different skin; it should only let them switch the mode of the skin chosen by the site owner.

CSS Pattern

Keep shared layout CSS in your normal theme CSS. Put visual differences in skins:

:root {
  --page-bg: #07111f;
  --panel-bg: #101a2b;
  --text: #f8fafc;
  --accent: #38bdf8;
}

body {
  background: var(--page-bg);
  color: var(--text);
}

.hero {
  background-image: url("/assets/images/skins/midnight-hero.jpg");
}

This keeps skins small and makes it easier to add more looks without duplicating pages or JavaScript.