Cookbook · 5 of 8

Sidebar only — floating Settings overlay

Canvas takes the full wrapper; Settings floats absolutely top-right with shadow + rounded corners. No widgets palette, no inline sidebar — "edit in place" feel for host pages where the canvas is primarily a viewer and edits are occasional. Click any canvas element → settings appear in the floating panel; click another → settings re-render. Same engine, different chrome silhouette.

Walkthrough

  1. Wrap two slots — canvas + floating settings, with a host toolbar above

    The --sidebar-only variant ships TWO slots: __canvas takes the full wrapper area, and __settings floats absolutely top-right (positioned by CSS, not by the grid). NO widgets palette — the wrapper passes widgetsContainer: null and the W6.A.A.1 null-tolerance contract skips that mount cleanly. Save / etc. live OUTSIDE the wrapper in a .demo-host-toolbar (variant has no header slot — same convention as 2-compact-2col and 3-compact-3col).

    HTML
    <div class="demo-host-toolbar">
        <span class="demo-host-toolbar__title">My builder</span>
        <button type="button" id="CookbookSidebarOnlySave">Save</button>
    </div>
    <div class="demo-mini-builder demo-mini-builder--sidebar-only">
        <div class="demo-mini-builder__canvas"   id="CookbookSidebarOnlyCanvas"></div>
        <div class="demo-mini-builder__settings" id="CookbookSidebarOnlySettings"></div>
    </div>
  2. Mount canvas + settings (null widgets)

    Canvas + settings each get a real DOM id; widgetsContainer is null. The W6.A.A.1 null-tolerance contract skips the widgets mount cleanly — no NPE, no console error. The engine boots in 'preview' mode (clean WYSIWYG, no debug overlays); buyers building an "edit in place" UX where canvas clicks surface settings opt in to design mode by uncommenting the setMode('design') line in the callback.

    JavaScript
    var builder = new Builder({
        mainContainer:     '#CookbookSidebarOnlyCanvas',
        widgetsContainer:  null,                              // no palette
        settingsContainer: '#CookbookSidebarOnlySettings',    // floating overlay
        historyUI:         false
    });
    
    builder.load(
        window.THEME_JSON,
        window.THEME_TEMPLATES,
        window.THEME_CONFIG_DATA,
        window.MEDIA_URL,
        function () {
            // builder.setMode('design');   // opt-in: edit-in-place UX
            wireSave(builder);
        }
    );
  3. Wire the host toolbar's Save button — getHtml() + getData()

    Same pattern as 2-compact-2col and 3-compact-3col: the toolbar lives outside the wrapper, so its Save button is reached the same way any other DOM element is — document.getElementById. The example below pops an alert() with the round-trip output; production hosts swap the alert for a fetch() POST to their backend.

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

Live demo

demo-mini-builder--sidebar-only
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 · 260 lines · 10.3 KB
/* style.css — cookbook/5-sidebar-only — `.demo-mini-builder--sidebar-only`.
 *
 * Self-contained chrome variant (W6.C.B.5). 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--sidebar-only` rules below ARE
 * the variant — copy them verbatim into your own stylesheet.
 *
 * ─── Buyer use case ─────────────────────────────────────────────────
 * "Edit in place" feel. Canvas takes the full wrapper area; the
 * Settings panel floats absolutely top-right with shadow + rounded
 * corners. No widgets palette, no inline sidebar — everything that
 * isn't the canvas hovers over it. Pick this when the host page is
 * primarily a viewer and edits are occasional / per-element rather
 * than continuous: a customer's CMS page, a marketing landing review,
 * a "preview-mostly + tweak when needed" UX.
 *
 * ─── No header slot, no widgets palette ────────────────────────────
 * Variant declares NO `__header` slot — Save / etc. live OUTSIDE the
 * wrapper in a `.demo-host-toolbar`, same convention as `2-compact-2col`
 * and `3-compact-3col`. Buyers learn ONE rule: if the variant has a
 * header slot, put chrome there; otherwise host toolbar above the
 * wrapper.
 *
 * The wrapper passes `widgetsContainer: null` — the W6.A.A.1 null-
 * tolerance contract skips the palette mount cleanly. Buyers who want
 * a palette pick `3-compact-3col` (inline column) or `4-rich-3col-
 * header` (inline column + in-wrapper header) instead.
 *
 * ─── Constructor (single null arg) ─────────────────────────────────
 *   new Builder({
 *       mainContainer:     '#YourCanvas',
 *       widgetsContainer:  null,            // no palette
 *       settingsContainer: '#YourFloatingSettings',
 *   });
 *
 * ─── Tokens consumed (already exposed by dist/builder.css) ──────────
 *   --bjs-bg, --bjs-bg-subtle, --bjs-border, --bjs-border-light,
 *   --bjs-radius-lg, --bjs-radius-md, --bjs-text, --bjs-text-secondary,
 *   --bjs-fs-sm, --bjs-fs-base, --bjs-space-2, --bjs-space-3,
 *   --bjs-space-5, --bjs-shadow, --bjs-primary.
 */

/* ─── Base wrapper (canvas-only fallback shape) ─────────────────────
 * Same canvas-only base as C.B.6 will use — single grid cell, the
 * variant's own rules layer the floating settings panel on top via
 * absolute positioning. The wrapper itself is `position: relative` so
 * the absolute settings panel anchors to it, not to the page <body>.
 */
.demo-mini-builder {
    display: grid;
    width: 100%;
    height: 520px;
    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__canvas,
.demo-mini-builder__settings {
    overflow: auto;
    min-height: 0;
    min-width: 0;
}

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

/* ─── Variant: --sidebar-only ── floating Settings overlay ─────────── */
.demo-mini-builder--sidebar-only {
    position: relative;
}

.demo-mini-builder--sidebar-only .demo-mini-builder__settings {
    /* Absolute positioning lifts the panel out of the grid flow so the
     * canvas can use the full wrapper width. `inset: <space-3> <space-3>
     * auto auto` parks the panel `<space-3>` from the top edge and
     * `<space-3>` from the right edge; `auto auto` on bottom + left lets
     * the panel size itself by content (capped by `max-height` below).
     *
     * `min-height` keeps the panel visually present even when it has
     * no selected element to render — without it, the panel collapses
     * to ~2 px (just the border) on first paint, which reads as "the
     * editor isn't ready" rather than "click an element to edit".
     */
    grid-area: unset;
    position: absolute;
    inset: var(--bjs-space-3) var(--bjs-space-3) auto auto;
    width: 280px;
    min-height: 96px;
    max-height: calc(100% - var(--bjs-space-5));
    border: 1px solid var(--bjs-border-light);
    border-radius: var(--bjs-radius-md);
    box-shadow: 0 4px 16px var(--bjs-shadow);
    background: var(--bjs-bg);
    overflow: auto;
    z-index: 10;
    /* Subtle entrance — the panel feels deliberate, not sudden, when
     * it appears on first paint or after a settings-driven re-render. */
    transition: box-shadow 160ms ease, transform 160ms ease;
}

/* Empty-state placeholder. The engine renders nothing into the
 * settings container until an element is selected. For "edit in
 * place" UX, a buyer hitting the page should see a short hint
 * telling them what to do — that the panel exists and edits start
 * from the canvas, not from the panel itself. `:empty` matches
 * exactly when the panel has zero children; once the engine
 * renders the first set of controls, the rule stops applying and
 * the placeholder disappears.
 */
.demo-mini-builder--sidebar-only .demo-mini-builder__settings:empty {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--bjs-space-3);
    text-align: center;
    color: var(--bjs-text-secondary);
    font-size: var(--bjs-fs-sm);
    line-height: 1.4;
}
.demo-mini-builder--sidebar-only .demo-mini-builder__settings:empty::before {
    content: "Click any element on the canvas to edit its settings here.";
}

/* ─── Host toolbar ─────────────────────────────────────────────────── */
.demo-host-toolbar {
    display: flex;
    align-items: center;
    gap: var(--bjs-space-2);
    margin-bottom: var(--bjs-space-3);
    font-size: var(--bjs-fs-sm);
    color: var(--bjs-text-secondary);
}

.demo-host-toolbar__title {
    margin-right: auto;
    font-weight: 600;
    color: var(--bjs-text);
    font-size: var(--bjs-fs-base);
}

.demo-host-toolbar 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: var(--bjs-radius-md);
    font: inherit;
    font-size: var(--bjs-fs-sm);
    line-height: 1.2;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}
.demo-host-toolbar button:hover {
    background: var(--bjs-bg-subtle);
    border-color: var(--bjs-border);
}
.demo-host-toolbar button:focus-visible {
    outline: 2px solid var(--bjs-primary);
    outline-offset: 2px;
}
.demo-host-toolbar button:active {
    transform: translateY(1px);
}
.demo-host-toolbar button[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
    .demo-mini-builder, .demo-mini-builder *,
    .demo-mini-builder--sidebar-only .demo-mini-builder__settings,
    .demo-host-toolbar button { transition: none; }
}
// init.js — cookbook/5-sidebar-only.
//
// Mounts a Builder instance into the `.demo-mini-builder--sidebar-only`
// 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
//
// Single null arg — `widgetsContainer: null` (no palette). The W6.A.A.1
// null-tolerance contract skips the widgets-mount cleanly. The
// Settings panel is wired to a real DOM id (the floating overlay).
// Save lives OUTSIDE the wrapper in a host toolbar (variant has no
// header slot — same convention as `2-compact-2col` / `3-compact-3col`).
//
// Engine boots in 'preview' mode → call `setMode('design')` after
// `load()` so the canvas is editable from first click. (See
// `cookbook/4-rich-3col-header/` for the discovery context.)

(function () {
    'use strict';

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

    var builder = new window.Builder({
        mainContainer:     '#CookbookSidebarOnlyCanvas',
        widgetsContainer:  null,                              // no palette
        settingsContainer: '#CookbookSidebarOnlySettings',    // floating overlay
        historyUI:         false
    });

    builder.load(
        window.THEME_JSON,
        window.THEME_TEMPLATES,
        window.THEME_CONFIG_DATA,
        window.MEDIA_URL,
        function () {
            // Engine default = 'preview' — clean canvas, no debug overlays.
            // 2026-05-09 — demo defaults to preview per the "no debug
            // mode unless this example IS the showcase" rule. Buyer
            // expecting clicks-to-edit-on-canvas behaviour adds
            // `b.setMode('design')` here. The settings-floating-overlay
            // appearance is the lesson; mode is an orthogonal choice.

            // Wire the host toolbar's Save button. Production host
            // replaces the alert with a fetch() POST to its own backend.
            var saveBtn = document.getElementById('CookbookSidebarOnlySave');
            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.cookbookSidebarOnlyBuilder = builder;
        }
    );
})();

Notes

Why the floating panel instead of an inline column? Two reasons. First, on dense canvases — services lineups, marketing pages, multi-column emails — the inline 240-280 px sidebar of 3-compact-3col eats real estate the buyer would rather see filled with content. Second, the floating panel signals a different UX intent: this is a viewer, edits are occasional. The 3-col layout signals continuous editing; the floating overlay signals tweak-in-place. Pick the silhouette that matches your host app's intent.

Why no widgets palette? The variant assumes the page tree already exists — buyer's customers / hosts created the page elsewhere (CMS, AI generator, template seeder). The cookbook's job here is to teach "edit what's on the canvas", not "add new blocks from scratch". If your buyers need to add blocks too, pick 3-compact-3col (inline palette) or 4-rich-3col-header (inline palette + header chrome). The widgetsContainer: null here exercises the W6.A.A.1 null-tolerance contract — engine skips the palette mount cleanly.

Why does the floating panel use position: absolute instead of fixed? Absolute anchors to the wrapper (which is position: relative), so the panel scrolls with the page when the buyer scrolls past the editor — same way an inline column does. fixed would pin the panel to the viewport and float it over unrelated page content below the wrapper, which breaks the "this is one editor area" mental model. If your host page IS a single full-bleed editor (no scrolling siblings), swap to fixed + adjust the inset values to viewport coordinates.

Why does the panel cap at calc(100% - var(--bjs-space-5)) max-height? The Settings panel renders one set of controls per element type (Image · Heading · Button etc.); on tall elements with many format controls (e.g. Image with all four corner radii + alt text + alignment), the panel can overflow. Capping max-height + inner overflow: auto makes the panel scroll its content rather than push past the wrapper's bottom edge. Drop the cap if you want the panel to grow to its natural content size — but accept that on small viewports it can extend below the canvas fold.

Why setMode('design') after load()? The engine boots in 'preview' mode (read-only WYSIWYG); a viewer-mostly UX still expects clicks on canvas elements to surface settings, which requires design mode. The "edit in place" framing applies to the chrome silhouette, not to the engine's internal mode — internally, the canvas is always in design mode while the buyer is on the page. (See 4-rich-3col-header's notes for the full discovery — boot mode + Preview-toggle pattern.)

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/AboutOurServices') and emits the four named globals before init.js loads. In your own project, render them from any backend — only the four names matter to the engine. See Quickstart for the canonical pattern.

Want a wider settings panel? Adjust the width rule in style.css. The default 280 px matches the format-control widths the engine uses internally; wider gives more room for long-form labels and dual-pane controls (e.g. spacing-around-the-box). Narrower is fine too if your host has limited canvas width — the panel's controls re-flow.