Skip to content

theme.yaml reference

Current schema version: manifestVersion: 1.

Manifest file: theme.yaml (preferred) or manifest.json. Unknown root fields are rejected for disk themes validated by the backend.

Field Required Description
manifestVersion yes Must be 1.
metadata yes Display name, description, author, version, tags, optional github.
configuration yes* Backdrop, stylesheet path, preview colors.
javascript no Sandboxed interactive backdrop.

* Disk themes must set configuration.source. Built-in repo themes embed CSS in the app binary.

Field Required Description
name yes Theme picker title.
description yes Short subtitle.
author yes Author or team.
version no Semver (recommended).
github no Source repository URL.
tags no Strings such as dark, animated, webgl.
Field Required Description
source yes (disk) Relative .scss, .sass, or .css. See Stylesheets.
backdrop no Windows shell material (Tauri).
colors no Picker preview: primary, background, surface, text.
Field Values Description
type mica, mica-alt, mica_alt, acrylic, blur, none Window backdrop effect.
theme light, dark Mica light/dark tint.

If javascript.enabled is true and javascript.layer is backdrop, Carbon does not apply Mica so the iframe canvas is visible behind the shell. If layer is overlay, Mica/backdrop from configuration.backdrop may still apply.

Field Required Description
enabled yes Enable sandbox iframe.
entry yes if enabled Relative path to one JS file. Max 256 KB (ThemeManifestSchema.MaxJavascriptBytes).
layer no Where the iframe sits in the stack (see below). Default: backdrop.
interactive no Desktop frontend only: forward pointer data as app events (kind: 'pointer'). Supported in bundled manifest.json in the repo. Not in the C# ThemeJavascriptSettings model, theme.yaml on disk cannot persist this field through the backend yet.

javascript.layer (backdrop vs overlay / “frontdrop”)

Section titled “javascript.layer (backdrop vs overlay / “frontdrop”)”
Value Implementation Description
backdrop THEME_LAYER_Z_INDEX.backdrop = 0 JavaScript draws behind the Carbon UI. Disables Mica while active.
overlay THEME_LAYER_Z_INDEX.overlay = 9000 JavaScript draws in front of the shell (colloquial “frontdrop”). Does not automatically disable Mica.

Invalid values are rejected at parse time (javascript.layer must be 'backdrop' or 'overlay').

Example overlay manifest (from backend tests):

javascript:
enabled: true
entry: dist/theme.js
layer: overlay

The iframe is mounted with position: fixed; inset: 0 and pointer-events: none unless the host enables the interactive pointer bridge.

After trim, the file must match:

CarbonTheme.mount(function (ctx) { ... });

Validated by validateThemeJavascript before injection.

validateThemeJavascript() strips comments and string literals, then applies these rules (themeJavascriptGuard.ts):

Rule id Blocked
postMessage postMessage in theme source (wrapper owns parent I/O)
window-parent window.parent, top, opener, frames
parent-top-ref parent.postMessage, parent.document, storage on parent, etc.
message-listener addEventListener('message'), onmessage =
broadcast BroadcastChannel, MessageChannel, MessagePort
network fetch(, XMLHttpRequest, WebSocket, EventSource
dynamic-import import(
workers Worker, SharedWorker, ServiceWorker
eval eval(, new Function(
storage localStorage, sessionStorage, indexedDB, openDatabase
cookie document.cookie
host-bridge __TAURI__, invoke(, webview, chrome., dotnet
nested-frame createElement('iframe'), srcdoc=, javascript: URLs
script-breakout </script in source

Do not rely on hiding APIs inside strings, the stripper removes most literals before scan.

  • Attribute: sandbox="allow-scripts" (no allow-same-origin).
  • CSP: no connect-src, no workers, inline scripts only.
  • Theme code runs in srcdoc with injected CarbonTheme wrapper first.
Format When Carbon loads the theme
.css Injected into #carbon-custom-theme unchanged.
.scss, .sass Compiled with Dart Sass (compileString), then injected.
Tailwind (@tailwind, @apply in unprocessed files) Not supported at load time. Compile to .css in your dev workflow and set source to the output.

Carbon never runs PostCSS, Tailwind CLI, or node_modules when applying a theme. SCSS is first-class; Tailwind is a valid authoring tool only if you ship compiled CSS (or plain SCSS without Tailwind directives).

Disk themes compile a single source file per load. Multi-file SCSS partials require a local build step or a monolithic entry file.

Used inside CarbonTheme.mount(function (ctx) { ... }):

Member Description
ctx.canvas Full-viewport 2D canvas; host sets DPR transform on resize.
ctx.root Full-viewport div for extra DOM (WebGL canvas, etc.).
ctx.settings.width / height / dpr Viewport size.
ctx.settings.reducedMotion From prefers-reduced-motion.
ctx.settings.paused True when document is hidden or host sent pause.
ctx.settings.seed Numeric seed from host.
ctx.tokens Map of --crbn-* computed values (strings).
ctx.random Seeded mulberry32 PRNG.
ctx.events.on(type, fn) Subscribe; returns unsubscribe function.
Type When
init After mount; includes settings, seed, tokens.
resize Window size or DPR change; default canvas resized.
pause / resume Tab visibility.
destroy Before iframe teardown.
tokens --crbn-* updated after CSS change.
app Host event messages (see below).

Themes must not register message listeners or call postMessage; the wrapper owns the bridge.

Delivered as ctx.events.on('app', function (ev) { ... }) when the host sends type: 'event':

Payload Meaning
{ type: 'theme-activated', themeId } Iframe passed validation and sent ready
{ type: 'theme-deactivated', themeId } Theme is being torn down
{ kind: 'pointer', phase, x, y, buttons, pressure } When javascript.interactive is true (phase: down / move / up / cancel)

Themes set CSS variables on :root. The shell reads these names (not Tailwind classes). Minimum useful set:

Tokens Purpose
--crbn-bg-deep, --crbn-bg, --crbn-bg-surface Layered backgrounds
--crbn-*-rgb variants Effects and JS ctx.tokens parsing
--crbn-text, --crbn-text-muted Body copy
--crbn-primary, --crbn-primary-rgb, --crbn-primary-hover Accent
--crbn-border, --crbn-border-hover Outlines
--crbn-error, --crbn-warning, --crbn-success, --crbn-info Status
--crbn-tb-* Title bar
--crbn-nb-* Sidebar / nav icons
--crbn-bb-* Status bar
--crbn-glass-opacity, --crbn-glass-blur Glass chrome

Full list: project/frontend/core/src/themes/default-dark/default-dark.scss.

There is no curated allow list. Bundled code must pass the static guard and fit in 256 KB.

Kind Examples Notes
3D / WebGL three, gl-matrix, twgl.js, regl Prefer tree-shaking; size is the limit.
Noise / motion simplex-noise, @tweenjs/tween.js Canvas motion without Three.
Color culori, chroma-js Parse/mix token colors.
Animation gsap (core, tree-shaken) Watch bundle size.
Heavy engines pixi.js, phaser Usually exceed cap.

Use Bun or npm only at build time: bun add → import in src/ → Bun.build → dist/theme.js. Carbon only reads the bundle string.

Mechanism Default
Ready timeout 5 s without ready → remove theme
Watchdog ping 15 s interval; 2 missed pongs → teardown
iframe navigation Security teardown
Theme switch destroy + iframe removal (500 ms grace)

See Theme engine architecture for the full pipeline and source file map.

The frontend calls generated ThemesApi methods (XPUI):

Method Purpose
ListDiskThemes() Manifest metadata for folders under Skeleton/Themes/
ReadStylesheet(themeId) Raw text of configuration.source
ReadJavascript(themeId) Raw text of javascript.entry (size-limited)

Stylesheet compilation happens in the browser (compileDiskThemeStylesheet), not on the server.

  • source and javascript.entry must be relative paths inside the theme folder.
  • .. segments and path escape are rejected.

Bump metadata.version when changing CSS or JS. Theme id = directory name under Skeleton/Themes/.

Page Link
Overview Themes
Authoring Create a theme pack
Engine architecture Runtime pipeline
Manifest reference This page
Built-in catalog Shipped themes