Brand
Custom mark — abstract silhouette + accent dot. Icon-only files; wordmark "BuilderJS" composed inline next to the icon. Locked 2026-05-07
Final mark
Single source of truth: /assets/brand/logo_light.svg
+ /assets/brand/logo_dark.svg. Theme-aware swap
via [data-theme] in app.css.
- Do let the wordmark inherit page
color; the "JS" override picks up--demo-accent. - Do swap variants via
[data-theme]attribute, not<picture>— keeps markup terse. - Don't recolour the mark itself, stretch its proportions, or split the icon from the wordmark in nav contexts.
Tokens
Every value below is read live from --demo-* custom
properties. Cloned from landing's --landing-* set on
2026-05-07; drift after that date is acceptable per
DESIGN.md §2.
Brand palette
primary
…
CTAs, Save button, primary actions
primary-fg
…
Foreground on top of `primary`
primary-hover
…
Primary CTA hover
accent
…
Accent dot, links, focus rings, "JS" wordmark
accent-soft
…
Pill / tag backgrounds (tinted accent)
accent-hover
…
Link hover
Surface & text
bg
…
Page background
bg-elevated
…
Cards · sidebars · popovers
text-strong
…
Hero headings · brand wordmark
text
…
Primary body · in-card paragraphs
text-secondary
…
Nav inactive · footer · captions
text-muted
…
Truly tertiary (timestamps, license)
border
…
Hairlines · dividers
Semantic
success
…
Toast variant · "shipped" check icons
warn
…
Toast variant · warnings
error
…
Toast variant · errors
Spacing (4 px baseline)
space-1
…
space-2
…
space-3
…
space-4
…
space-5
…
space-6
…
space-7
…
space-8
…
space-9
…
space-10
…
Radius
radius-sm
…
radius-md
…
radius-lg
…
radius-xl
…
radius-pill
…
Shadow
shadow-sm
Buttons, chips
shadow-md
Cards on hover
shadow-lg
Popovers, modals
Code highlight
Syntax-highlight palette + Prism.js integration. Used by every
.demo-code-popover across demo — quickstart steps,
cookbook walkthroughs, dev-docs snippets. Light = One Light (Atom);
dark = One Dark Pro (VS Code), saturation pulled back ~10% so the
palette reads muted-pro, not candy-rainbow.
Token palette
Side-by-side light + dark twins, regardless of the page's current theme. AA-contrast verified for every main token (comment is intentionally below AA — matches GitHub / One Light / One Dark Pro de-emphasis convention).
Light — One Light
-
bg#fafbfccode block background -
fg#383a42default code text -
comment#a0a1a7// comments, /* */, # bash, italic -
keyword#a626a4function · return · const · class · import -
string#50a14f"hello" · 'world' · `template` -
number#98680142 · 3.14 · 0xff · CSS units -
builtin#d75f00window · document · console · Builder -
function#4078f2function names at call site -
class-name#c18401class Foo · new Bar() · type names -
property#e45649object keys · CSS properties -
tag#e45649HTML/JSX tags <div> -
attr-name#986801HTML attrs class= · href= -
attr-value#50a14fHTML attribute values -
operator#383a42= · + · - · ?: · && -
punctuation#383a42, · ; · [] · () · {} -
variable#e06c75PHP $var · JS template ${ } -
deleted#e45649diff "-" lines, soft red bg -
inserted#50a14fdiff "+" lines, soft green bg -
selector#a626a4CSS selectors .foo · #bar -
important#a626a4!important · async · await
Dark — One Dark Pro
-
bg#1c1f26code block background -
fg#abb2bfdefault code text -
comment#5c6370// comments, /* */, # bash, italic -
keyword#c678ddfunction · return · const · class · import -
string#98c379"hello" · 'world' · `template` -
number#d19a6642 · 3.14 · 0xff · CSS units -
builtin#e5c07bwindow · document · console · Builder -
function#61afeffunction names at call site -
class-name#e5c07bclass Foo · new Bar() · type names -
property#e06c75object keys · CSS properties -
tag#e06c75HTML/JSX tags <div> -
attr-name#d19a66HTML attrs class= · href= -
attr-value#98c379HTML attribute values -
operator#56b6c2= · + · - · ?: · && -
punctuation#abb2bf, · ; · [] · () · {} -
variable#e06c75PHP $var · JS template ${ } -
deleted#e06c75diff "-" lines, soft red bg -
inserted#98c379diff "+" lines, soft green bg -
selector#c678ddCSS selectors .foo · #bar -
important#c678dd!important · async · await
Language samples
Each pair shows the same snippet rendered with the light palette (top) and the dark palette (bottom) so reviewers see both modes without flipping the page theme.
PHP
<?php
declare(strict_types=1);
require __DIR__ . '/_lib/ThemeRegistry.php';
$bundle = (new \DemoBuilder\ThemeRegistry(__DIR__ . '/themes'))
->resolveBundle('default', 'master/sample/email/Welcome');
// Hand the four named globals to the buyer's frontend.
header('Content-Type: text/html; charset=utf-8');
echo "<script>window.THEME_JSON = " . json_encode($bundle->themeJson) . ";</script>";
<?php
declare(strict_types=1);
require __DIR__ . '/_lib/ThemeRegistry.php';
$bundle = (new \DemoBuilder\ThemeRegistry(__DIR__ . '/themes'))
->resolveBundle('default', 'master/sample/email/Welcome');
// Hand the four named globals to the buyer's frontend.
header('Content-Type: text/html; charset=utf-8');
echo "<script>window.THEME_JSON = " . json_encode($bundle->themeJson) . ";</script>";
JavaScript
const builder = new Builder({
mainContainer: '#editor',
widgetsContainer: '#widgets',
settingsContainer: '#settings',
historyUI: false,
});
builder.load(THEME_JSON, THEME_TEMPLATES, THEME_CONFIG_DATA, MEDIA_URL, () => {
console.log('Builder ready — page has', builder.getData().blocks.length, 'blocks.');
});
const builder = new Builder({
mainContainer: '#editor',
widgetsContainer: '#widgets',
settingsContainer: '#settings',
historyUI: false,
});
builder.load(THEME_JSON, THEME_TEMPLATES, THEME_CONFIG_DATA, MEDIA_URL, () => {
console.log('Builder ready — page has', builder.getData().blocks.length, 'blocks.');
});
TypeScript
interface BuilderConfig {
mainContainer: string;
widgetsContainer?: string | null;
historyUI: boolean;
}
const config: BuilderConfig = {
mainContainer: '#editor',
widgetsContainer: null,
historyUI: false,
};
const builder: Builder = new Builder(config);
interface BuilderConfig {
mainContainer: string;
widgetsContainer?: string | null;
historyUI: boolean;
}
const config: BuilderConfig = {
mainContainer: '#editor',
widgetsContainer: null,
historyUI: false,
};
const builder: Builder = new Builder(config);
HTML
<!doctype html>
<html lang="en" data-theme="light">
<head>
<link rel="stylesheet" href="/dist/builder.css">
</head>
<body>
<div id="editor" class="demo-mini-builder__canvas"></div>
<script src="/dist/builder.js" defer></script>
</body>
</html>
<!doctype html>
<html lang="en" data-theme="light">
<head>
<link rel="stylesheet" href="/dist/builder.css">
</head>
<body>
<div id="editor" class="demo-mini-builder__canvas"></div>
<script src="/dist/builder.js" defer></script>
</body>
</html>
CSS
.demo-btn--primary {
display: inline-flex;
align-items: center;
padding: var(--demo-space-2) var(--demo-space-4);
background: var(--demo-primary);
color: var(--demo-primary-fg);
border-radius: var(--demo-radius-pill);
transition: background 120ms ease;
}
.demo-btn--primary:hover { background: var(--demo-primary-hover) !important; }
.demo-btn--primary {
display: inline-flex;
align-items: center;
padding: var(--demo-space-2) var(--demo-space-4);
background: var(--demo-primary);
color: var(--demo-primary-fg);
border-radius: var(--demo-radius-pill);
transition: background 120ms ease;
}
.demo-btn--primary:hover { background: var(--demo-primary-hover) !important; }
JSON
{
"name": "PageElement",
"template": "Page",
"formats": {
"background_color": "#ffffff",
"padding_top": 24,
"padding_bottom": 24
},
"blocks": [
{ "name": "BlockElement", "template": "Hero" }
]
}
{
"name": "PageElement",
"template": "Page",
"formats": {
"background_color": "#ffffff",
"padding_top": 24,
"padding_bottom": 24
},
"blocks": [
{ "name": "BlockElement", "template": "Hero" }
]
}
Bash
#!/usr/bin/env bash
# Build BuilderJS bundle and run the demo server.
set -euo pipefail
npm install
npm run build
php -S localhost:8000 -t demo/ &
echo "demo ready at http://localhost:8000"
#!/usr/bin/env bash
# Build BuilderJS bundle and run the demo server.
set -euo pipefail
npm install
npm run build
php -S localhost:8000 -t demo/ &
echo "demo ready at http://localhost:8000"
Edge cases
Diff blocks (delete + insert), long-line overflow (default
horizontal-scroll vs. --wrap soft-wrap), and inline
<code> in body copy.
Diff (insert + delete)
- mainContainer: '#editor',
- widgetsContainer: '#widgets',
+ mainContainer: '#editor',
+ widgetsContainer: null,
+ settingsContainer: null,
+ historyUI: false,
- mainContainer: '#editor',
- widgetsContainer: '#widgets',
+ mainContainer: '#editor',
+ widgetsContainer: null,
+ settingsContainer: null,
+ historyUI: false,
Long line — overflow vs. soft-wrap
builder.events.on('save', (data) => fetch('/api/builder/save', { method: 'POST', body: JSON.stringify({ themeJson: data.themeJson, themeTemplates: data.themeTemplates, configData: data.configData, savedAt: Date.now() }) }));
builder.events.on('save', (data) => fetch('/api/builder/save', { method: 'POST', body: JSON.stringify({ themeJson: data.themeJson, themeTemplates: data.themeTemplates, configData: data.configData, savedAt: Date.now() }) }));
Inline <code> in a paragraph stays as the host
font's monospace surface (e.g. builder.getData()
returns a JSON-shaped object) so it doesn't compete with full
.demo-code-popover blocks for attention. Only
.demo-code-popover instances pick up the syntax-
highlight palette.
Typography
Inter as the variable-axis primary,
Roboto as a system-fallback companion. Body sets
font-feature-settings: 'cv11', 'ss03', 'tnum', 'calt'
so every glyph picks up Inter's modern single-storey "a", humanist
"g", tabular numerals, and contextual ligatures. Mailchimp-tier
crispness target —
see DESIGN.md §10.
Heading scale
The quick brown fox
h1 · 48px · display 800 · ls -0.025em
The quick brown fox
h2 · 32px · bold 700 · ls -0.018em
The quick brown fox
h3 · 24px · bold 700
The quick brown fox
h4 · 20px · bold 700
The quick brown fox
h5 · 18px · bold 700
The quick brown fox
h6 · 16px · bold 700
Body sample
BuilderJS is a drag-drop email and page builder you ship inside
your own product. Buyers extract the source package, point PHP's
built-in server at demo/, and immediately see a
professional showcase home, a top-notch live builder, a design
system, runnable backend examples, and a full developer docs
portal — all without leaving the package.
Default body weight is --demo-fw-body (450) on
--demo-fs-base (16px) with --demo-lh-body
(1.55) and --demo-ls-body (-0.005em). Numerals are
tabular: 1234567890. Ligatures run:
fi · ff · ffi · => · !=.
Weight ladder
regular · 400
Default body — never bold-shouty
body · 450
Confident-not-loud body weight
medium · 500
Form labels, nav inactive
semibold · 600
Brand wordmark, active nav, CTAs
bold · 700
Section headings (h2)
display · 800
Page-hero h1 only
Inline elements
Open /builder.php, edit $builderConfig
(the single PHP↔JS handoff array — D6.1), save the file,
reload, and the builder reflects your integration parameters.
Press ⌘+S in the canvas to trigger the Save
flow without reaching for the mouse.
Numerals & tabular alignment
| Wave | Items | Effort | Status |
|---|---|---|---|
| W0 | 15 | S · 1 session | Shipped |
| W1 | 09 | S · 1 session | This wave |
| W2 | 33 | XL · 2–3 sessions | Pending |
| W3–W7 | 39 | L–M each | Pending |
| W8 | 20 | XL · 2–3 sessions | Pending |
Components
Chrome-level primitives used across demo/*. Class
names are stable; W2 builder UI consumes the same selectors so the
Save button, panel chrome, and form controls inherit one design
language.
Buttons
Primary uses --demo-primary (black light · white dark).
Hover lifts to --demo-primary-hover; focus rings on
--demo-accent. Loading state replaces the icon with a
spinner — never a blank state.
Pills & tags
Pills earn --demo-fw-semibold + caps tracking because
small-text uppercase needs weight to read. Used for status (e.g.,
"Coming in W4"), counts, and badges in chrome cards.
Cards
Quick start
Drop one <script> tag, mount the builder
into a container, and you're shipping. The full walkthrough
lands in examples/basic/1-quickstart/ at W4.
Custom theme
Theme directories drop into demo/themes/. Each
holds an index.html template and one or more
JSON samples — same shape as the bundled
themes/default/.
Resting cards sit on --demo-bg-elevated; interactive
cards (.demo-card--interactive) lift to
--demo-shadow-md on hover and accept keyboard focus.
Form inputs
.demo-code-popover — copy-pasteable code chip ✓ shipped (W4)
Buyer-package primitive. Dark surface (fixed in both themes) with
language label · copy button · scrollable code body. Lives at
demo/assets/css/code-popover.css; consumed by every
example walkthrough's code blocks. Click "Copy" — JS toggles
.is-copied for ~1.6s.
const builder = new Builder({ mainContainer: '#MyBuilder' });
builder.load(THEME_JSON, THEME_TEMPLATES, THEME_CONFIG_DATA, MEDIA_URL);
.demo-more — disclosure widget for advanced/reference info ✓ shipped (W6)
Native <details>/<summary> styled to keep
walkthrough flow simple. Quick-glance buyer doesn't drown; curious
buyer clicks to expand. Three tones — info (default),
--neutral (subtle muted, for structural notes),
--warn (caveats / "watch out"). Used by W6 example
walkthroughs for "shape reference" / "edge cases" / "production
swap matrix" — anything optional for the quick-run reader.
Lives at demo/assets/css/app.css.
Need the full shape of each load() arg?
click to expand
Default tone. Lead icon: lightbulb. Use case:
field-shape reference for a step the buyer just read at
quick-glance. Click to expand shows the four builder.load()
args + an example shape per arg + which file on disk it comes from.
Why a PHP file, not pure HTML? click to expand
--neutral tone — subtle muted band, pre-opened
here for the showcase. Lead icon: info. Use case:
structural aside that doesn't break the buyer's flow but
explains the "why" behind a step.
Heads up — production checklist click to expand
--warn tone — used for caveats. Lead icon:
warning. Use case: a "watch out"
the buyer should see before shipping (rate limits, CSRF,
asset upload size caps, env var requirements, etc.).
.demo-mini-builder — chrome-variant primitive ✓ shipped (W6)
Layout-only wrapper for hosting a Builder in a constrained space —
cookbook chrome variants, example pages, dashboard tiles. 8 variant
modifiers (--minimal · --canvas-only ·
--compact-2col · --compact-3col ·
--rich-3col-header · --sidebar-only ·
--mobile · --card). Lives at
demo/assets/css/mini-builder.css; live demos per variant
ship in Phase C.B cookbook (each cookbook entry mounts a real Builder
with the matching null-container combo via W6.A.A.1). Below: static
chrome shapes, no live Builders — see
B1 quickstart for a real one.
--minimal--compact-2col--compact-3col--rich-3col-header--sidebar-only--canvas-only--mobile--card