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; }"
}
FieldRequiredDescription
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.
extendsId of a theme to inherit from. Tokens you don't set come from it, then from PopCraft Dark's defaults.
tokensToken values; see the token reference. Unknown tokens are ignored, so a theme keeps working if a token is renamed.
fontsFonts the theme bundles; see below.
cssExtra CSS; see below.
Make a theme of your own and change its accent · 0:06
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:

  1. The PopCraft Dark defaults.
  2. Each theme in the extends chain, oldest ancestor first.
  3. The theme's own tokens.
  4. 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:

  • accent also sets accent.hover and focusRing.
  • fg also sets the text ramp (fg.bright through fg.faint), blended toward the panel color.
  • panel.bg also 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() or rgba(), 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) and var(--font-nunito).
  • Switches: motion.enabled is "1" or "0". canvas.handle.strokeWidth is 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" }
]
FieldRequiredDescription
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: @import and url() values other than data: URIs are removed, so themes can't load anything from the network.