CBDF Specification: Layout Catalogue

Version 1.0 (Phase II starter)

Document: 10-Layout-Catalogue • Date: 2026-07-18

1. Purpose

The layout catalogue tells people which page skeleton to pick and tells encoders which pane is which index when writing CBDF. The wire carries only a uint16 LayoutID; geometry and pane tables live in the client catalogue (same idea as the font table).

Machine file (download / ship with clients):

2. Wire field

Styles section payload begins with:

[LayoutID: 2 bytes little-endian]

Example: named layout email-header-footer has id 257 (0x0101) → bytes 01 01.

3. ID ranges

RangeCountMeaning
0x0000–0x00FF256Legacy compatibility: low byte = old 1-byte panel/col/row bitfield
0x0100–0x013150Named desktop starters (blog, dashboard, shop, email, …)
0x0200–0x02FF256Mobile stacks for each legacy id
0x0300–0x033150Mobile stacks for each named desktop
0x0400+Reserved for catalogue growth

Starter set size: 612 layout records (256+256 legacy pairs + 50+50 named pairs). Unique desktop skeletons: 256 legacy + 50 named = 306 (within “up to 255 named unique” spirit for the curated set; legacy fills the full byte).

4. Pane index table (canonical rule)

Unless a layout’s catalogue entry overrides the order, panes are indexed:

  1. Header (if present)
  2. Left aside (if present)
  3. Main cells, row-major (left→right, top→bottom)
  4. Right aside (if present)
  5. Footer (if present)
  6. Overlay (modal/alert island — always last if present)

Encoders open STYLE_CONTAINER for indices 0 .. pane_count-1 in ascending order. See each layout’s panes[] in the JSON for the authoritative list.

Legacy layout bitfield

Legacy bitfield used when LayoutID is in 0x0000–0x00FF

5. Mobile / responsive pairing

Every desktop layout has a pair.mobile_layout_id. Mobile layouts re-stack panes to a single column:

header → main cells (row-major) → left → right → footer → overlay

Default breakpoint: 768 CSS px (default_mobile_breakpoint_px in the JSON). Clients MAY:

  • Keep the desktop LayoutID on the wire and switch rendering to the paired mobile id at the breakpoint, or
  • Encode a mobile-only document using the mobile LayoutID directly.

Side rails become stacked sections (not overlapping columns). Full-height flags are cleared on mobile stacks.

6. Navigation bars

Nav is content inside a pane, not a separate layout bit.

Field (JSON)Meaning
nav.placementnone | header | left | right | footer
nav.pane_indexWhich pane hosts the nav (matches a pane with nav_host: true)
nav.orientationhorizontal (typical header) or vertical (typical left rail)
nav.item_block_typeAlways 2 = CBDF ITEM_BLOCK nav
nav.mobile_collapsehamburger_header | drawer_left | bottom_tabs | stack | hidden

6A. Text encoding for a nav bar

[STYLE_CONTAINER pane_index]     ; nav host pane
  [ITEM_BLOCK 0x02 nav_style_index]
    [LINK_START …] Home [LINK_END]
    US
    [LINK_START …] About [LINK_END]
    US
    [LINK_START …] Contact [LINK_END]
  [BLOCK_END]
[BLOCK_END]

nav_style_index references the Nav Bar Styles sub-table in Styles (orientation, colors, hover, collapse breakpoint, icon/text mode).

6B. Recommended host by layout type

  • Marketing / email / blog: horizontal nav in header (collapse: hamburger).
  • Docs / admin / dashboard: vertical nav in left (collapse: drawer).
  • Mobile app shell: nav in footer as bottom tabs.
  • Plain email body: nav.placement = none.

7. Named desktop starters (summary)

Full pane tables: NAMED-LAYOUTS.md. Categories include:

  • email / qmail — simple body, header/footer, newsletter columns, receipt
  • blog / docs — left or right sidebar, full-height docs nav, TOC rail
  • dashboard / app — left rail, inbox list+detail, settings, kanban, map+tools
  • landing / marketing — hero, split-screen, pricing 3-col, contact split
  • ecommerce — filters + products, product detail split
  • grid / gallery — 2×2, 3×2, 3-col, 4-col, portfolio
  • overlay — center modal, auth card
  • document — invoice, report cover/body, legal long-form
HexSlugTitle
0x0100email-simpleSimple single column
0x0101email-header-footerEmail with header and footer
0x0103blog-right-sidebarBlog / docs with right sidebar
0x0104blog-left-sidebarBlog / docs with left sidebar
0x0106dashboard-left-railDashboard with full-height left rail
0x0109split-screen-2Split screen two columns
0x010Cgrid-2x22×2 main grid
0x0110shop-filters-leftShop with left filters
0x0118modal-centerMain with center modal overlay
0x0122holy-grailHoly grail (H L M R F)
0x012Dinbox-list-detailInbox list + detail
See NAMED-LAYOUTS.md for all 50

8. JSON schema (layout object)

{
  "id": 259,
  "hex": "0x0103",
  "slug": "blog-right-sidebar",
  "title": "Blog / docs with right sidebar",
  "category": "blog",
  "viewport": "desktop",
  "pane_count": 4,
  "panes": [
    { "index": 0, "role": "header", "nav_host": true, ... },
    { "index": 1, "role": "main", "grid_col": 0, "grid_row": 0, ... },
    { "index": 2, "role": "right", ... },
    { "index": 3, "role": "footer", ... }
  ],
  "nav": {
    "placement": "header",
    "orientation": "horizontal",
    "pane_index": 0,
    "item_block_type": 2,
    "mobile_collapse": "hamburger_header"
  },
  "pair": { "mobile_layout_id": 771 },
  "ascii_preview": "...",
  "use_cases": ["blog post", "documentation"]
}

9. Encoder checklist

  1. Choose layout by slug or id from the catalogue.
  2. Write LayoutID as uint16 LE in Styles.
  3. For each pane index in order, open STYLE_CONTAINER with the composite style for that pane.
  4. Put nav ITEM_BLOCK type=2 in the nav_host pane.
  5. Main multi-cell grids: emit one container per main cell in row-major order as listed.
  6. Optional: set Required Features bit 11 if LayoutID ≥ 0x0100 and fidelity matters.

10. Legacy bitfield reference

For LayoutID 0x00XX, decode low byte:

  • Bit 0 header, bit 1 footer, bit 2 left, bit 3 right
  • Bits 4–5 columns (1–4), bits 6–7 rows (1–4)
  • Main always present; pane indices follow the canonical rule

All 256 values are pre-expanded in the JSON so encoders need not re-implement the bitfield unless they want to.

11. Growth

New layouts get the next free id in 0x0100+ (desktop) and matching 0x0300+ mobile pair. Registry is append-only. Private experiments use 0xF000–0xFFFE and must not appear in interoperable mail without agreement.

12. Custom

The LayoutID space has room for 65,000+ layouts. If you would like to submit your own layout to the standard — and are willing to allow everyone to use it — we will charge $45 to add it to the standard catalogue.

New additions take some time to propagate, since clients ship with a local copy of the catalogue. However, we plan to list all layouts in the DRD (the directory service), so that when a client encounters a LayoutID that is not in its local cache, it can ask the directory for the entry. Once this is set up in Phase III, clients will receive new layouts instantly.

12A. How to describe your layout

A layout submission is one JSON layout object in the format defined in section 8 above — the same format every entry in cbdf-layout-catalog-v1.json uses. Copy an existing entry that is close to your idea (see NAMED-LAYOUTS.md for the human-readable index) and edit it. Your submission must include:

  • name and slug — a short unique name (e.g. recipe-card);
  • panes[] — every pane in canonical index order (section 4), each with its role (header / left / main cell / right / footer / overlay) and default geometry hints;
  • nav — placement, host pane, orientation, and mobile collapse behavior (section 6), or none;
  • mobile pair — the single-column stacking order for the paired mobile layout (section 5); we assign the paired mobile ID with your desktop ID;
  • category — where it belongs in the picker (email, blog, shop, dashboard, …).

Do not pick your own LayoutID — IDs are assigned by the registry on acceptance. A sketch or screenshot of the intended result helps review but the JSON object is the submission.