JustFlows

Theme development

Build a block-first theme

A Justflows theme is a portable design system: CSS, tokens, block templates, reusable template parts, patterns, and optional demo layouts. Public pages use the core renderer—there are no executable PHP or EJS theme templates.

Theme anatomy

Project structure
acme-theme/
├── justflows.json
├── justflows-theme.json
├── styles/global.css
├── templates/
│   ├── index.json
│   ├── front-page.json
│   └── single.json
├── parts/
│   ├── header.json
│   └── footer.json
├── patterns/
└── demo/

The three files below are one worked example, Studio — a theme with a full color palette, a Customizer-contributed accent picker, per-block CSS-variable controls, and one landing pattern. Copy it whole and delete what you don’t use.

Styles

styles/global.css, components.css, and blocks.css — whichever exist — are concatenated into /theme.css, in that order.

Templates & parts

templates/*.json and parts/header.json / footer.json are found by filename, not declared in the manifest — see the hierarchy below.

Patterns

patterns/*.json are insertable designs. List them under patterns in justflows-theme.json for a stable id → file mapping, or omit the map and every patterns/*.json file is picked up by its filename.

Demo

demo/home.json, blog.json, header.json, and footer.json seed a new site’s front page, blog index, and default chrome. Also found by filename, not declared.

justflows-theme.json and justflows.json

justflows-theme.json (runtime metadata)
{
  "id": "acme.studio",
  "name": "Studio",
  "version": "1.0.0",
  "description": "A block-first marketing theme with a landing pattern, a full design-token palette, and a Customizer-driven accent color.",
  "author": "Acme",
  "license": "GPL-2.0-or-later",
  "engines": { "justflows": ">=0.2.0 <0.3.0" },
  "cssVariables": {
    "--color-primary": "#4f46e5",
    "--color-primary-hover": "#4338ca",
    "--color-bg": "#ffffff",
    "--color-surface": "#f8fafc",
    "--color-text": "#0f172a",
    "--color-muted": "#64748b",
    "--color-border": "#e2e8f0",
    "--max-width": "720px",
    "--max-width-wide": "1140px"
  },
  "styles": ["styles/global.css"],
  "patterns": {
    "landing": "./patterns/landing.json"
  },
  "supports": { "blockEditor": true, "customColors": true, "customFonts": true, "patterns": true },
  "blockControls": {
    "core.color-scheme": [
      "--jf-color-scheme-active-bg",
      "--jf-color-scheme-active-fg",
      "--jf-color-scheme-hover-bg",
      "--jf-color-scheme-hover-fg",
      "--jf-color-scheme-hover-border"
    ]
  },
  "customize": {
    "brand": {
      "label": "Brand",
      "controls": {
        "--color-accent": { "label": "Accent color", "type": "color", "default": "#f59e0b" }
      }
    }
  }
}
justflows.json (archive root)
{
  "schemaVersion": 1,
  "type": "theme",
  "id": "acme.studio",
  "name": "Studio",
  "version": "1.0.0",
  "publisher": "Acme",
  "description": "A block-first marketing theme with a landing pattern, a full design-token palette, and a Customizer-driven accent color.",
  "license": "GPL-2.0-or-later",
  "engines": { "justflows": ">=0.2.0 <0.3.0" },
  "permissions": [],
  "registry": {
    "commercialMarketplace": false,
    "listed": true,
    "free": true,
    "comingSoon": false,
    "category": "Marketing",
    "tags": ["landing", "marketing", "dark-mode"],
    "screenshots": []
  }
}

justflows-theme.json, field by field

id / name / version
Same dot-namespaced id and SemVer version as the archive manifest.
description / author
Display text in the theme picker and Marketplace. Not read by the renderer.
license / engines
A GPL-compatible license (required) and the justflows host-version range this build supports.
cssVariables
The theme’s default design tokens. The Customizer starts every site on these values before an owner overrides any of them.
styles
Informational — records which stylesheets this theme ships (used when the admin “Save as new theme” forks it). /theme.css itself always concatenates the fixed filenames styles/global.css, components.css, blocks.css, whichever are present — this array does not choose them.
patterns
Maps a pattern id to its file, e.g. "landing": "./patterns/landing.json". Omit the map entirely and every patterns/*.json file is registered by its filename instead — both work; the map exists so an id can differ from the filename.
supports
Declarative capability flags shown in the theme picker today. The host does not yet gate any behavior on them — do not rely on flipping one to turn a feature on or off.
blockControls
Promotes chosen CSS custom properties on a block type to first-class inspector fields, keyed by block type — { "core.button": ["--my-var", ...] }. Authors then restyle a block instance with no CSS.
customize
Adds Customizer sections. Each control key must itself be a CSS custom property; type is one of color, range, select, or font — nothing else validates.

Template hierarchy

The renderer builds an ordered list of candidate slugs for the request and uses the first templates/&lbrace;slug&rbrace;.json the theme actually ships, always ending in index. There is one chain per kind of request — they do not chain into each other:

text
Home, set to a static page   →  front-page → page-{slug} → page → singular → index
Home, set to the post feed   →  front-page → home → index
A "page"-type content row    →  page-{slug} → page → singular → index
Any other content row        →  single-{type}-{slug} → single-{type} → single → singular → index
Nothing matched the URL      →  404 → index

Every template is a block document. Context blocks such as core.post-title and core.post-content resolve against the current request, while the content row’s own blocks fill the page body.

Patterns

patterns/landing.json
{
  "schemaVersion": 1,
  "id": "landing",
  "title": "Landing page",
  "description": "Hero, proof, and call to action",
  "category": "marketing",
  "version": "1.0.0",
  "requiresBlockTypes": ["acme.forms.form"],
  "blocks": [
    {
      "id": "hero",
      "type": "core.heading",
      "version": 1,
      "props": { "level": 1, "text": "Ship faster with Studio" }
    },
    {
      "id": "cta-form",
      "type": "acme.forms.form",
      "version": 1,
      "props": { "formId": "contact" }
    }
  ]
}

Every non-core block type used in blocks must be listed in requiresBlockTypes — the pattern fails validation otherwise. The builder shows an installation notice instead of importing a broken design when a listed type isn’t installed. Patterns feed the categorized block-pattern library — theme-width previews, editable insertion, locale variants, and validated JSON import/export. Plugins contribute the same shape at runtime with ctx.patterns.register().

Headers

Headers are a library of named designs with one site default, edited in Theme builder → Header and served from /api/headers (the old /api/header-presets is gone). Themes and plugins contribute designs through the header.templates, header.resolve, and header.config hooks — a theme manifest itself declares nothing for headers.

Menus

Admin → Menus is a visual designer with layouts (horizontal, dropdown, multi-level-dropdown, mega, footer, drawer, …), a per-menu mobile breakpoint and collapse pattern, per-item styling, and server-side visibility rules. A theme ships the front-end for it — partials/nav-menu.ejs, /js/site-nav.js, and .jf-nav styles in global.css — and the shared data-jf-devices primitive is emitted into every theme’s /theme.css. Contribute one-click layout presets through the menu.design.presets filter (static MenuDesignSeed data, no build(); ids must be "&lbrace;themeId&rbrace;:&lbrace;slug&rbrace;"). The host re-parses whatever you return, so a preset can never widen what the designer allows.

Design tokens and cascade

Expose documented custom properties for colors, spacing, type, widths, radii, and shadows. This allows the Customizer and third-party plugins to inherit the active visual language.

  1. Theme CSS
  2. Customizer tokens and platform animation CSS
  3. Active plugin styles
  4. Site owner’s Additional CSS

Package and test

Validate every JSON document, confirm the fallback templates/index.json exists, test missing optional plugins, preview responsive layouts, and verify both light and dark modes before creating the archive.

Package from the theme directory
COPYFILE_DISABLE=1 tar -czf ../../acme-studio.jfpkg \
  justflows.json justflows-theme.json styles templates parts patterns demo