Cookbook · 1 of 8

Minimal — canvas + thin top strip

No widgets palette, no settings sidebar — just a 36 px header strip for a title or a Save button, and the editing canvas. Pick this when your host app already supplies the chrome.

Walkthrough

  1. Wrap two slots — header + canvas

    Drop one .demo-mini-builder.demo-mini-builder--minimal wrapper anywhere in your page. Inside, two slots: __header for your title / Save button, and __canvas for the editing surface. Give the canvas slot an id; init.js mounts the Builder there.

    HTML
    <div class="demo-mini-builder demo-mini-builder--minimal">
        <div class="demo-mini-builder__header">
            <span style="margin-right: auto; font-weight: 600;">My builder</span>
            <button type="button" id="CookbookMinimalSave">Save</button>
        </div>
        <div class="demo-mini-builder__canvas" id="CookbookMinimalCanvas"></div>
    </div>
  2. Mount with both sidebars set to null

    W6.A.A.1 null-tolerance — passing null for widgetsContainer and settingsContainer makes the engine skip those sub-system mounts cleanly. No NPE, no console error. That contract is exactly what makes --minimal a one-slot wrapper.

    JavaScript
    var builder = new Builder({
        mainContainer:     '#CookbookMinimalCanvas',
        widgetsContainer:  null,   // <- null-tolerance: engine skips
        settingsContainer: null,   // <- null-tolerance: engine skips
        historyUI:         false
    });
    
    builder.load(
        window.THEME_JSON,
        window.THEME_TEMPLATES,
        window.THEME_CONFIG_DATA,
        window.MEDIA_URL
    );
  3. Wire the Save button — getHtml() + getData()

    The header strip is yours — title text, Save button, anything you want. The example below pops an alert() with the round-trip output; production hosts swap the alert for a fetch() POST to their backend. getHtml() returns the rendered page (string); getData() returns the page tree (object).

    JavaScript
    document.getElementById('CookbookMinimalSave').addEventListener('click', function () {
        var html = builder.getHtml();
        var data = builder.getData();
        alert(
            'Saved · ' + html.length + ' chars HTML, ' +
            Object.keys(data).length + ' top-level page-tree keys.'
        );
    });

Live demo

demo-mini-builder--minimal
My builder

The whole snippet

Click Copy on any file to grab it byte-identical, or Expand to read inline (capped at ~520 px so the page stays scannable). Multi-file snippets surface a side-by-side grid — copy only the files you need.

2 files · 181 lines · 6.5 KB
/* style.css — cookbook/1-minimal — `.demo-mini-builder--minimal`.
 *
 * Self-contained chrome variant (W6.C.B.1). Pair this file with init.js
 * (sibling) and dist/builder.css + dist/builder.js, and you have a fully-
 * working Builder mount in 4 files. NO shared mini-builder.css. NO
 * examples.css. The `.demo-mini-builder--minimal` rules below ARE the
 * variant — copy them verbatim into your own stylesheet.
 *
 * ─── Buyer use case ─────────────────────────────────────────────────
 * Embed inside an iframe (or any constrained slot in an existing app)
 * where the host already supplies its own chrome. The mini-builder
 * just needs a thin top strip for a title or a Save button + the
 * canvas itself. No widgets palette, no settings sidebar.
 *
 * ─── Constructor null-args (W6.A.A.1 null-tolerance) ────────────────
 *   new Builder({
 *       mainContainer:     '#YourCanvas',
 *       widgetsContainer:  null,   // ← no widgets palette
 *       settingsContainer: null,   // ← no settings sidebar
 *   });
 * The engine skips the omitted mounts cleanly — no NPE, no console
 * error, regardless of whether DOM nodes exist for them.
 *
 * ─── Tokens consumed (already exposed by dist/builder.css) ──────────
 *   --bjs-bg, --bjs-bg-subtle, --bjs-border, --bjs-border-light,
 *   --bjs-radius-lg, --bjs-text, --bjs-text-secondary,
 *   --bjs-fs-sm, --bjs-fs-base, --bjs-space-2, --bjs-space-3.
 */

/* ─── Base wrapper (4-slot grid; canvas-only fallback) ─────────────── */
.demo-mini-builder {
    display: grid;
    width: 100%;
    height: 480px;
    border: 1px solid var(--bjs-border);
    border-radius: var(--bjs-radius-lg);
    background: var(--bjs-bg);
    color: var(--bjs-text);
    font-size: var(--bjs-fs-base);
    overflow: hidden;
    box-sizing: border-box;
    grid-template-areas: "canvas";
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
}

.demo-mini-builder *,
.demo-mini-builder *::before,
.demo-mini-builder *::after { box-sizing: border-box; }

.demo-mini-builder__header,
.demo-mini-builder__canvas {
    overflow: auto;
    min-height: 0;
    min-width: 0;
}

.demo-mini-builder__header {
    grid-area: header;
    background: var(--bjs-bg-subtle);
    border-bottom: 1px solid var(--bjs-border-light);
    padding: var(--bjs-space-2) var(--bjs-space-3);
    display: flex;
    align-items: center;
    gap: var(--bjs-space-2);
    font-size: var(--bjs-fs-sm);
    font-weight: 500;
    color: var(--bjs-text-secondary);
}

/* Minimal button reset for any <button> inside the header strip.
 * Keeps the walkthrough HTML clean (`<button>Save</button>` — no class)
 * while still giving the buyer a sensible, token-driven look. */
.demo-mini-builder__header button {
    appearance: none;
    -webkit-appearance: none;
    border: 1px solid var(--bjs-border-light);
    background: var(--bjs-bg);
    color: var(--bjs-text);
    padding: 4px 10px;
    border-radius: 6px;
    font: inherit;
    font-size: var(--bjs-fs-sm);
    line-height: 1.2;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}
.demo-mini-builder__header button:hover {
    background: var(--bjs-bg-subtle);
    border-color: var(--bjs-border);
}
.demo-mini-builder__header button:focus-visible {
    outline: 2px solid var(--bjs-primary);
    outline-offset: 2px;
}
.demo-mini-builder__header button:active {
    transform: translateY(1px);
}

.demo-mini-builder__canvas {
    grid-area: canvas;
    background: var(--bjs-bg);
    position: relative;
}

/* ─── Variant: --minimal ──── thin top strip + canvas ─────────────── */
.demo-mini-builder--minimal {
    grid-template-areas:
        "header"
        "canvas";
    grid-template-rows: 36px 1fr;
}

@media (prefers-reduced-motion: reduce) {
    .demo-mini-builder, .demo-mini-builder * { transition: none; }
}
// init.js — cookbook/1-minimal.
//
// Mounts a Builder instance into the `.demo-mini-builder--minimal`
// scaffold rendered alongside this file. Reads four named globals
// (BUILDER.md RULE I split-globals convention):
//
//     window.THEME_JSON        — page tree (the sample JSON)
//     window.THEME_TEMPLATES   — { templateKey: HTML EJS string }
//     window.THEME_CONFIG_DATA — theme's index.json verbatim
//     window.MEDIA_URL         — base URL for relative asset paths
//
// The host page emits these (server-rendered via PHP `ThemeRegistry::
// resolveBundle()` in this demo; production hosts can hardcode them or
// pre-render from any backend). The engine consumes them in the same
// order as the named globals — no wrapper object.
//
// W6.A.A.1 null-tolerance — passing `null` for `widgetsContainer` and
// `settingsContainer` makes the engine skip those sub-system mounts
// cleanly. No NPE, no console error, regardless of whether the
// corresponding DOM nodes exist. That contract is what makes
// `--minimal` chrome possible — only canvas + header in the buyer's
// HTML, nothing else.

(function () {
    'use strict';

    var canvas = document.getElementById('CookbookMinimalCanvas');
    if (!canvas || typeof window.Builder !== 'function') return;

    var builder = new window.Builder({
        mainContainer:     '#CookbookMinimalCanvas',
        widgetsContainer:  null,
        settingsContainer: null,
        historyUI:         false
    });

    builder.load(
        window.THEME_JSON,
        window.THEME_TEMPLATES,
        window.THEME_CONFIG_DATA,
        window.MEDIA_URL,
        function () {
            // Wire the Save button in the header strip. Production
            // host replaces the alert with a fetch() POST to its own
            // backend. getHtml() returns the rendered email/page;
            // getData() returns the page tree for round-trip reload.
            var saveBtn = document.getElementById('CookbookMinimalSave');
            if (saveBtn) {
                saveBtn.addEventListener('click', function () {
                    var html = builder.getHtml();
                    var data = builder.getData();
                    alert(
                        'Saved · ' + html.length + ' chars HTML, ' +
                        Object.keys(data).length + ' top-level page-tree keys.'
                    );
                });
            }

            // Expose for buyer-side debugging + spec assertions.
            window.cookbookMinimalBuilder = builder;
        }
    );
})();

Notes

Why two null arguments? Builder.js's constructor accepts widgetsContainer + settingsContainer as optional. Passing null tells the engine: "don't mount these sub-systems." Internally (W6.A.A.1 null-tolerance, src/includes/Builder.js) the engine guards every reach into those sub-systems with a presence check, so the rest of the runtime — selection, history, save, getHtml — works identically to the full 3-column chrome. No mocks, no orphan <div>s, no warnings.

What happens if I drop the header? You can. Remove the __header slot from the HTML and the wrapper falls back to "canvas"-only via the base .demo-mini-builder rules. If you want a TRULY chrome-less surface (no border, no rounded corners, no padding), pick the 6-canvas-only preset instead — it strips the wrapper border entirely.

Where do the four window.* globals come from? The host page renders them server-side. In this demo, _partials/example.php calls ThemeRegistry::resolveBundle('default', 'master/sample/email/Minimal') and emits the four named globals before init.js loads. In your own project, render them from any backend (PHP, Node, Python, Go) — only the four names matter to the engine. See Quickstart for the canonical pattern.

Save flow? The demo above pops an alert() so the round-trip is visible without a backend. Production hosts replace the alert with a fetch(saveUrl, { method: 'POST', body: JSON.stringify({ html, data }) }). Hello World wires the full POST + response toast pattern.