Creating a theme
The easiest way to make a theme is the theme editor. This page describes the file it produces, for when you want to write or generate themes by hand.
{
"id": "acme.themes.midnight",
"name": "Midnight",
"type": "dark",
"extends": "popcraft.dark",
"tokens": {
"accent": "#7c5cff",
"accent.hover": "#9479ff",
"panel.bg": "#15151f",
"canvas.selection": "#7c5cff"
},
"css": "[data-pc=\"section-title\"] { letter-spacing: 0.08em; text-transform: uppercase; }"
}| Field | Required | Description |
|---|---|---|
id | ✓ | Unique id: letters, digits, ., _, -. Plugin themes must start with the plugin id. |
name | ✓ | Shown in the theme menu. |
type | ✓ | "dark" or "light". Exposed as data-theme-type on <html> for CSS. |
extends | Id of a theme to inherit from. Tokens you don't set come from it, then from PopCraft Dark's defaults. | |
tokens | Token values; see the token reference. Unknown tokens are ignored, so a theme keeps working if a token is renamed. | |
fonts | Fonts the theme bundles; see below. | |
css | Extra CSS; see below. |
Description
The theme editor on the built-in PopCraft Dark, which is read-only. Duplicate makes an editable copy named Pop Sunset. Its accent token is changed to an orange, and every accent in the app — the selected tool, buttons, the selection — turns orange at once.
Inheritance
A theme is resolved in this order:
- The PopCraft Dark defaults.
- Each theme in the
extendschain, oldest ancestor first. - The theme's own
tokens. - Unsaved edits in the theme editor.
The built-in comic themes both extend popcraft.comic-base. It holds their shared shape, fonts and CSS, and isn't
listed in the menu. Extend popcraft.comic-yellow or popcraft.comic-black to make your own comic variant.
Derived colors
Setting a base color fills in its companions, so a small theme still looks coherent:
accentalso setsaccent.hoverandfocusRing.fgalso sets the text ramp (fg.brightthroughfg.faint), blended toward the panel color.panel.bgalso sets the surface and border ramps (field,control,raised,overlay,border…).
Any companion you set yourself wins. Canvas tokens are never derived. The theme editor marks derived tokens so you can see which ones follow their base.
Values
- No code in values: token values can't contain
;,{,},<or>. - Canvas tokens:
canvas.*tokens must be#rgb,#rrggbb,#rrggbbaa,rgb()orrgba(), because the GPU can't read other formats. - Fonts: use font families that are installed, bundled with PopCraft, or bundled with the theme (below).
Bangers and Nunito are always available, as
var(--font-bangers)andvar(--font-nunito). - Switches:
motion.enabledis"1"or"0".canvas.handle.strokeWidthis a number from 0.5 to 4.
Fonts
A theme can carry its own fonts, so it looks the same on every machine and offline:
"fonts": [
{ "family": "Comic Neue", "src": "data:font/woff2;base64,d09GMgAB…", "weight": "700" }
]| Field | Required | Description |
|---|---|---|
family | ✓ | The name to use in font.ui / font.heading, e.g. "'Comic Neue', sans-serif" |
src | ✓ | A base64 data: URL. Remote URLs aren't allowed. Each font can be up to 2 MB. |
weight | "400", or a range for variable fonts, "100 900" | |
style | "normal" or "italic" |
In the theme editor, Fonts → Add font… turns a .woff2, .woff, .ttf or .otf file into this for you.
Custom CSS
css is added after PopCraft's own styles, inside @layer pc-theme, so it wins over the default styling without
needing !important in most cases.
- Selectors: target the CSS hooks and use
var(--pc-*)tokens, so your CSS follows token edits. - Offline only:
@importandurl()values other thandata:URIs are removed, so themes can't load anything from the network.