Skip to content
Build a theme B5 6 min read Customisation

Layout best practice

Patterns that don't break — hierarchy, palette cap, padding cascade.

You've shipped a sample (B3) and a theme (B4). Now you'll meet the rules that keep both robust. These are distilled from THEME_BEST_PRACTICE.md (2800 LOC of audit history); the buyer-tier version below is what you need to ship and not regress.

The hierarchy is sacred

Every page is one of two stacks. Memorise both.

Code
PageElement
└── BlockElement (a row)
    └── ElementGroup
        └── PElement | HeadingElement | ImageElement | ButtonElement | …
Three things follow:
  • You can never put a leaf element directly inside PageElement — it must be wrapped in a BlockElement. The Block owns the row's vertical rhythm.
  • You can never put a leaf element directly inside a GridElement — it must be wrapped in a CellElement then a BlockElement. Each column has its own internal stack.
  • You can never nest a Grid inside a Grid — break the inner Grid up into multiple Blocks, or use a single Grid with more cells.

Container width matrix

Surface Container width Why
Email 600 px Industry-standard since Outlook 2007. Wider than 600 risks horizontal scroll in Apple Mail / iOS narrow window.
Form / checkout 480 px Forms read better at narrower widths — less eye-jump per field.
Landing page 1200 px Modern desktop reading width with adequate gutter.

The shipped samples respect these widths via Page.template.html's outermost <table width> value. When you author a sample of a given kind, don't override the width unless you have a specific reason — buyers will see it in the wrong context (a 1000-px-wide email looks broken in Outlook).

Padding cascade

BlockElement carries the page's vertical rhythm:

`padding_top` and `padding_bottom` only — never `padding_left` / `padding_right` at this layer (the page container's `formats.padding_*` owns horizontal). Never margin (RULE-H11; margins collapse unpredictably across email clients).

The visual rhythm rule: rows should have uneven top/bottom padding only if they're acting as section separators (a hero row above + a body row below probably shares 20-30 px between them; a CTA row gets 30-40 px). Even padding (top = bottom) reads as a uniform stack; varied padding reads as deliberate sections.

The 4-colour palette cap

A sample (or theme) uses at most 4 distinct chromatic colours. Greyscale doesn't count toward the cap.

Code
Acceptable: black, white, brand red, off-white grey, sub-text grey
            └─ chromatic ─┘  └─ chromatic ─┘  └─── greyscale ───┘
            That's 2 chromatic + 1 greyscale family. Pass.
The reason: a sample with 5+ distinct hues reads as untrustworthy (Geocities-era). Buyers who paid for premium templates won't tolerate it. Keep your accent + secondary + alarm tones to one chromatic family per role.

The 5-typography-tier cap

A sample uses at most 5 typography tiers. The canonical set:

Tier Font size (px) Weight Use
H1 32-36 700 One per page. Hero headline.
H2 22-24 600 Section heading.
H3 18 600 Sub-section heading.
Body 14-16 400 Paragraphs.
Caption 12 400 Footer text, fine print.

If you find yourself reaching for an 18 px H4 + a 16 px H5 in the same email, you've broken the cap. Fix: collapse to 4 tiers, or use one tier for both with bold/regular weight as the differentiator.

Edge alignment invariants

Every element edge in a Block must align with the page container's content edge OR with another element's edge. Three concrete rules:

  • Headings + paragraphs are flush-left (or centered, for hero blocks). Never indented except via the Block's container padding.
  • Images at full content width are flush-left + flush-right. If the image is narrower than content, it's centered.
  • Buttons are centered or flush-left, never floating arbitrarily.

This is the difference between "designed" and "thrown together" — buyers spot the misalignment instantly even if they can't articulate why.

Explicit units

Every dimension is an integer in pixels. Never strings:

Percentages exist only on `CellElement.formats.width` (cells in a Grid sum to 100%) and on `Image.formats.align` ("center" / "left" / "right" — that's a literal alignment, not a typed unit).

When to use a Grid vs stacked Blocks

Use a GridElement when:

  • The row has logically parallel content in N columns (image + text split, 3-up product gallery).
  • Each column has its own vertical rhythm (multi-block content per column).

Use stacked BlockElements when:

  • The content is sequential down the page (most emails — hero → body → CTA → footer).
  • One row would feel artificial if columned (a single CTA button below a hero is a Block, not a 1-column Grid).

When in doubt: stack Blocks. Grids cost more rendering (more EJS template invocations, more nested HTML), so don't reach for them when a Block does the job.

Common mistakes the audit catches

"My sample looks fine on desktop, broken on mobile" — almost always a fixed-pixel width on a Grid or an Image that exceeds 600 px. Email clients don't responsively scale by default; your samples must be designed for the narrowest realistic viewport.

"My new theme renders correctly but is rejected by Litmus's 22-client matrix" — you probably edited Page.template.html and dropped a Outlook-conditional comment block. Restore the original wrapper structure; brand changes go in formats overrides.

"My H1 + H2 + H3 + H4 are all defined and the sample renders weird" — you've broken the 5-tier cap. Pick 5; promote the others to bold variants of an existing tier.

"My gradient background looks great in DevTools but ghosts in Outlook" — Outlook ignores most CSS3. Use solid colours for backgrounds; if you need a gradient, bake it into a hero image, not a CSS rule.

Did you make it?

You should now be able to:

  • ✅ Recite the two valid hierarchy stacks without looking.
  • ✅ Pick container width by surface (email vs form vs page).
  • ✅ State the 4-colour + 5-typography caps + the units rule.
  • ✅ Decide between Grid vs stacked Blocks by content shape.

What's next?

  • Build a custom Element ⭐ — graduate from theme to engine: subclass BaseElement and ship a Countdown component.
  • 🔗 THEME_BEST_PRACTICE.md — the canonical 2800-LOC rule library this doc distils.