Themes overview
Themes change Carbon itself: colors, glass surfaces, the Windows backdrop (Mica), optional sidebar icons, and optional live canvas / WebGL behind the UI. They do not patch Roblox client files. For in-game assets, use mods.
Built-in themes ship in project/frontend/core/src/themes/. Community themes install as folders under Skeleton/Themes/<theme-id>/. The folder name is the theme id. Carbon discovers disk themes at startup and lists them next to built-ins in Settings → Appearance.
Lifecycle
Section titled “Lifecycle”- Discover: Built-in themes are bundled with the app. The backend scans
Skeleton/Themes/*fortheme.yamlormanifest.json. - Select: You pick a theme in Settings. The choice is persisted (
AppSettingsstyle id + local storage fallback). - Load stylesheet: Carbon injects compiled CSS into
#carbon-custom-themeon:root(data-themeon<html>). - Backdrop: On Windows (Tauri),
configuration.backdropdrives Mica / acrylic unless a backdrop-layer JS theme is active (then Mica is skipped so your canvas shows through). - Optional JavaScript: If
javascript.enabled, the host validatesdist/theme.js, mounts a sandboxed iframe, and runsCarbonTheme.mount(function (ctx) { … }). - Switch / teardown: Changing themes sends
destroy, removes the iframe, and applies the next token pack.
Three theme shapes
Section titled “Three theme shapes”| Shape | What you ship | Examples |
|---|---|---|
| Token pack | SCSS or CSS that sets --crbn-* on :root |
Carbon Dark, Polaris, Neon District |
| Token pack + icons | Stylesheet + icons/*.svg overrides |
Carbon Light |
| Token pack + JS | Stylesheet + bundled javascript.entry |
Frosted, Slipstream, Deep Lattice |
JavaScript is optional. Most themes are stylesheet-only.
Stylesheets: SCSS, CSS, and Tailwind
Section titled “Stylesheets: SCSS, CSS, and Tailwind”Carbon’s UI reads design tokens (--crbn-bg, --crbn-primary, …), not utility class names from your theme file.
| Format | Supported at load time | Notes |
|---|---|---|
.scss / .sass |
Yes | Disk themes: Dart Sass compiles the single configuration.source entry when loaded. Repo built-ins compile via Vite (partials and @use work there). |
.css |
Yes | Loaded as-is. Good for output from your own build pipeline. |
| Tailwind CSS | Not at load time | Carbon does not run PostCSS or Tailwind when applying a theme. Use Tailwind (or any tool) while authoring to emit plain .css (usually :root { --crbn-*: … }) and point configuration.source at that file. Do not put @tailwind directives in a file you expect Carbon to compile with Sass only, they will not expand. |
SCSS is the usual choice in the repo: variables, nesting, and shared color math in one my-theme.scss. For disk themes with many partials, either keep one entry file or precompile SCSS/Tailwind to dist/theme.css in your package script (same idea as bundling dist/theme.js).
JavaScript backdrops (summary)
Section titled “JavaScript backdrops (summary)”Interactive themes run in an iframe (sandbox="allow-scripts", strict CSP). You get ctx.canvas (2D), ctx.root (extra DOM / WebGL canvas), ctx.tokens, host events (init, resize, pause, …), and a 256 KB cap on the bundle. WebGL works by creating a canvas on ctx.root (see Deep Lattice in the repo).
Backdrop vs in front of the UI (“frontdrop”)
Section titled “Backdrop vs in front of the UI (“frontdrop”)”Carbon does not use a manifest value named frontdrop. You choose where the iframe draws with javascript.layer:
layer |
Where it draws | Typical use |
|---|---|---|
backdrop (default) |
Behind the Carbon shell (z-index: 0) |
Particles, aurora, WebGL void behind glass panels |
overlay |
In front of the shell (z-index: 9000) |
Full-screen effects, vignettes, or visuals on top of the UI |
javascript: enabled: true entry: dist/theme.js layer: overlay # “frontdrop”: manifest keyword is overlay, not frontdropMica / window backdrop: when layer is backdrop, Carbon skips Windows Mica so your canvas is visible. When layer is overlay, configuration.backdrop (Mica, acrylic, etc.) can still apply behind everything.
The iframe stays pointer-events: none by default so clicks reach the app. Optional javascript.interactive: true (read by the desktop frontend) forwards pointer events to your theme via host app events, useful for overlay experiments; most shipped themes leave it off.
Details: Authoring and Manifest reference.
Themes vs mods
Section titled “Themes vs mods”| Themes | Mods | |
|---|---|---|
| Target | Carbon desktop shell | Roblox client content |
| Manifest | theme.yaml / manifest.json |
mod.yaml |
| Install path | Skeleton/Themes/<id>/ |
User mods folder (app UI) |
| Animation | Optional sandboxed JS / WebGL | N/A |
UI texture packs such as ingame-menu-pack are mods, not themes.
Documentation map (sidebar)
Section titled “Documentation map (sidebar)”Every item under Themes in the docs sidebar has a dedicated page:
| Sidebar label | Page | Contents |
|---|---|---|
| Overview | Themes | Lifecycle, SCSS/Tailwind, JS layers, mods vs themes |
| Authoring | Authoring | Folders, SCSS/Tailwind build, JS/WebGL, Bun bundles |
| Engine architecture | Architecture | Backend discovery, iframe host, bridge, lifecycle |
| theme.yaml reference | Reference | Manifest fields, ctx API, guard rules, tokens |
| Built-in themes | Built-in | Shipped themes in the repo |