Design system

Brand, tokens, typography, and components — single source of truth for every demo/* page. Every primitive consumed elsewhere is rendered here in both light and dark, so reviewers can audit the language without grepping CSS.

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.

BuilderJS logo on light surface
BuilderJS logo on dark surface
BuilderJS
BuilderJS
16 · faviconpx
24px
32 · navpx
48px
16 · faviconpx
24px
32 · navpx
48px
Construction
3 organic mark paths + 1 circle accent dot. SVG viewBox 205.78 × 206.
Light variant
Black mark + #24455C dot (deep navy).
Dark variant
#F2F2F2 mark + #41799D dot (teal accent).
Used in
nav lockup · favicon · builder chrome (W2) · design system header.
  • 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 #fafbfc code block background
  • fg #383a42 default code text
  • comment #a0a1a7 // comments, /* */, # bash, italic
  • keyword #a626a4 function · return · const · class · import
  • string #50a14f "hello" · 'world' · `template`
  • number #986801 42 · 3.14 · 0xff · CSS units
  • builtin #d75f00 window · document · console · Builder
  • function #4078f2 function names at call site
  • class-name #c18401 class Foo · new Bar() · type names
  • property #e45649 object keys · CSS properties
  • tag #e45649 HTML/JSX tags <div>
  • attr-name #986801 HTML attrs class= · href=
  • attr-value #50a14f HTML attribute values
  • operator #383a42 = · + · - · ?: · &&
  • punctuation #383a42 , · ; · [] · () · {}
  • variable #e06c75 PHP $var · JS template ${ }
  • deleted #e45649 diff "-" lines, soft red bg
  • inserted #50a14f diff "+" lines, soft green bg
  • selector #a626a4 CSS selectors .foo · #bar
  • important #a626a4 !important · async · await

Dark — One Dark Pro

  • bg #1c1f26 code block background
  • fg #abb2bf default code text
  • comment #5c6370 // comments, /* */, # bash, italic
  • keyword #c678dd function · return · const · class · import
  • string #98c379 "hello" · 'world' · `template`
  • number #d19a66 42 · 3.14 · 0xff · CSS units
  • builtin #e5c07b window · document · console · Builder
  • function #61afef function names at call site
  • class-name #e5c07b class Foo · new Bar() · type names
  • property #e06c75 object keys · CSS properties
  • tag #e06c75 HTML/JSX tags <div>
  • attr-name #d19a66 HTML attrs class= · href=
  • attr-value #98c379 HTML attribute values
  • operator #56b6c2 = · + · - · ?: · &&
  • punctuation #abb2bf , · ; · [] · () · {}
  • variable #e06c75 PHP $var · JS template ${ }
  • deleted #e06c75 diff "-" lines, soft red bg
  • inserted #98c379 diff "+" lines, soft green bg
  • selector #c678dd CSS 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

Light
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>";
Dark
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>";

JavaScript

Light
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.');
});
Dark
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.');
});

TypeScript

Light
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);
Dark
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);

HTML

Light
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>
Dark
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

Light
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; }
Dark
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; }

JSON

Light
JSON
{
    "name": "PageElement",
    "template": "Page",
    "formats": {
        "background_color": "#ffffff",
        "padding_top": 24,
        "padding_bottom": 24
    },
    "blocks": [
        { "name": "BlockElement", "template": "Hero" }
    ]
}
Dark
JSON
{
    "name": "PageElement",
    "template": "Page",
    "formats": {
        "background_color": "#ffffff",
        "padding_top": 24,
        "padding_bottom": 24
    },
    "blocks": [
        { "name": "BlockElement", "template": "Hero" }
    ]
}

Bash

Light
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"
Dark
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"

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)

Light
Diff
- mainContainer: '#editor',
- widgetsContainer: '#widgets',
+ mainContainer:     '#editor',
+ widgetsContainer:  null,
+ settingsContainer: null,
+ historyUI:         false,
Dark
Diff
- mainContainer: '#editor',
- widgetsContainer: '#widgets',
+ mainContainer:     '#editor',
+ widgetsContainer:  null,
+ settingsContainer: null,
+ historyUI:         false,

Long line — overflow vs. soft-wrap

Default — horizontal scroll
JavaScript
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() }) }));
.demo-code-popover--wrap — soft-wrap
JavaScript
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.

Family
Inter, Roboto, system-ui, -apple-system, "Segoe UI", sans-serif
Mono
"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace
Smoothing
antialiased · text-rendering: optimizeLegibility

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

BuilderJS — drag-drop builder regular · 400 Default body — never bold-shouty
BuilderJS — drag-drop builder body · 450 Confident-not-loud body weight
BuilderJS — drag-drop builder medium · 500 Form labels, nav inactive
BuilderJS — drag-drop builder semibold · 600 Brand wordmark, active nav, CTAs
BuilderJS — drag-drop builder bold · 700 Section headings (h2)
BuilderJS — drag-drop builder 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 plan progress (Inter tabular numerals)
WaveItemsEffortStatus
W015S · 1 sessionShipped
W109S · 1 sessionThis wave
W233XL · 2–3 sessionsPending
W3–W739L–M eachPending
W820XL · 2–3 sessionsPending

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

Default Accent Success Warning Error

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.

5 min

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/.

15 min

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

Save format

.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.

JavaScript
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
▸ Save
canvas
--compact-2col
canvas
Settings
--compact-3col
Widgets
canvas
Settings
--rich-3col-header
LogoSave · Undo · Redo
Widgets
canvas
Settings
--sidebar-only
canvas (full-bleed)
Edit
--canvas-only
canvas (no chrome)
--mobile
canvas
--card
Quick edit
canvas (dashboard tile)