--- SECTION: INTRO --- # Email Builder — Introduction & LLM Reference Overview This document is the entry point for understanding the email builder system. Read it before consulting any individual component reference. --- ## What This Builder Does This is a **code-driven email builder** that generates HTML email templates compatible with all major email clients, including Outlook Classic (Word rendering engine), Gmail, Apple Mail, and mobile clients. Templates are defined as a JSON `DocumentTree` — a tree of `ComponentNode` objects — which the builder compiles to table-based HTML with inline styles, MSO conditional comments, and responsive media queries. --- ## Document Model Every email is a tree of **ComponentNodes**. Each node has: ```json { "id": "unique-string", "type": "ComponentType", "config": { /* component-specific properties */ }, "bindings": { /* optional: data layer connections */ }, "children": [ /* optional: child ComponentNodes */ ] } ``` The tree always starts with an `Html` root and follows a strict hierarchy. See **SECTION: TEMPLATE** for the full structural contract. --- ## Component Types ### Structural (document skeleton) | Component | Role | |---|---| | `Html` | Document root. Contains `Head` and `Body`. | | `Head` | Email ``: meta tags, fonts, global CSS resets. | | `Body` | Email canvas: sets width, background, and centers content. | | `Section` | Full-width horizontal band. Exactly 3 per template (`header`, `content`, `footer`). | ### Layout (arrange content) | Component | Role | |---|---| | `Container` | Primary layout unit. Direct child of `Section`. Distributes children horizontally into columns. Cannot nest inside another Container. | | `Column` | Vertical stacking primitive inside a Container cell. Freely nestable. | | `Row` | Horizontal arrangement primitive. Freely nestable. | ### Leaf (content) | Component | Role | |---|---| | `Text` | Styled HTML text. Primary typography primitive. | | `Heading` | Semantic headings (`h1`–`h6`) with full text styling. | | `Image` | Responsive, email-compatible image with optional link. | | `Button` | Clickable CTA button with Outlook VML support. | | `Icon` | Iconify-powered icon with background and link support. | | `Divider` | Horizontal rule for separating content. | | `Spacer` | Fixed-height vertical whitespace. | | `Font` | Web font declaration for the `Head`. Not a content node. | --- ## Hierarchy at a Glance ``` Html └── Body └── Section (×3, fixed: header / content / footer) └── Container (stacks vertically; no nesting) └── Column / Row (freely nestable) └── Text / Image / Button / Heading / Icon / Divider / Spacer ``` **Key constraints:** - There are **always exactly 3 Sections** per template — never more, never fewer. - A **Container cannot be nested inside another Container** under any circumstance. - **Column and Row can nest freely** inside each other and inside Container cells. - **Leaf nodes are terminal** — they have no structural children. For the full nesting rule set, see **SECTION: TEMPLATE**. --- ## Data Layer & Bindings Any `ComponentNode` from `Section` downward can carry a `bindings` object to connect it to the Data Layer — a structured JSON object provided at render time. ```json "bindings": { "dataList": "products", "itemAlias": "product", "visible": "user.isPremium === true", "propertyMap": { "config.src": "product.image.src", "config.alt": "product.image.alt" } } ``` See **SECTION: DATA-BINDING** for the full reference. --- ## Reference Sections | Section | Contents | |---|---| | `SECTION: INTRO` | This file. System overview and orientation. | | `SECTION: TEMPLATE` | Full structural contract: nesting rules, hierarchy, the 3-section model. | | `SECTION: DATA-BINDING` | Data Layer, `bindings` object, `propertyMap`, `dataList`, `visible`. | | `SECTION: HTML` | `Html` component reference. | | `SECTION: HEAD` | `Head` component reference. | | `SECTION: BODY` | `Body` component reference. | | `SECTION: SECTION` | `Section` component reference. | | `SECTION: CONTAINER` | `Container` component reference. | | `SECTION: COLUMN` | `Column` component reference. | | `SECTION: ROW` | `Row` component reference. | | `SECTION: TEXT` | `Text` component reference. | | `SECTION: HEADING` | `Heading` component reference. | | `SECTION: IMAGE` | `Image` component reference. | | `SECTION: BUTTON` | `Button` component reference. | | `SECTION: ICON` | `Icon` component reference. | | `SECTION: DIVIDER` | `Divider` component reference. | | `SECTION: SPACER` | `Spacer` component reference. | | `SECTION: FONT` | `Font` component reference. | --- SECTION: TEMPLATE --- # Template — LLM Reference A **template** is the complete structure of an email document. It is the top-level JSON object that the builder reads, renders, and exports. Every template is a plain object with three top-level fields: `bodyStyle`, `tree`, and an optional `dataLayer`. The `tree` is an array of exactly three `Section` nodes — `header`, `content`, and `footer` — in that order, always. > **Note:** `Html`, `Head`, and `Body` are not part of the document object. They are internal rendering concerns handled by the builder. The document you author and store contains only `bodyStyle`, `dataLayer`, and `tree`. --- ## Document Shape ``` { bodyStyle: { … } ← Body styling: width, background, padding, font dataLayer: { … } ← (optional) data for bindings tree: [ Section (sectionType: "header") ← always index 0, never moves Section (sectionType: "content") ← always index 1, never moves Section (sectionType: "footer") ← always index 2, never moves ] } ``` The three Sections are **fixed structural slots**. They are not added or removed — they are always present. Their order is immutable. The builder uses `sectionType` to identify each slot; the slots themselves are not interchangeable. --- ## Full Document JSON Structure ```json { "bodyStyle": { "backgroundColor": "#ffffff", "fontFamily": "sans-serif" }, "dataLayer": {}, "tree": [ { "id": "section-header", "type": "SectionComponent", "config": { "sectionType": "header", "backgroundColor": "#ffffff", "padding": "0" }, "children": [] }, { "id": "section-content", "type": "SectionComponent", "config": { "sectionType": "content", "backgroundColor": "#ffffff", "padding": "0" }, "children": [] }, { "id": "section-footer", "type": "SectionComponent", "config": { "sectionType": "footer", "backgroundColor": "#f9fafb", "padding": "0" }, "children": [] } ] } ``` --- ## Nesting Rules These rules are absolute. Violating them produces invalid templates. ### Section - There are **exactly three** Sections per template: `header`, `content`, `footer`. - **`sectionType` is mandatory** — every Section must declare `"sectionType": "header" | "content" | "footer"` in its `config`. - Sections **cannot be nested** inside each other or inside any other component. - Sections **cannot be added or removed** — they are always present. - Sections **cannot be reordered** — `header` is always first, `content` second, `footer` last. - The only **allowed children** of a Section are `Container` nodes. ### Container - A `Container` is always a **direct child of a Section**. - Containers **cannot be nested** inside another Container, directly or indirectly. - Multiple Containers can exist inside one Section — they stack **vertically**. - The only **allowed parent** of a Container is a Section. ### Column and Row - `Column` and `Row` live **inside a Container** cell, below the Container level. - `Column` and `Row` **can nest freely and infinitely** inside each other. - Neither `Column` nor `Row` can be a direct child of a Section. ### Leaf components - `Text`, `Image`, `Button`, `Heading`, `Icon`, `Divider`, `Spacer` are leaf nodes. - They live inside `Column`, `Row`, or directly in a `Container` cell. - They cannot have structural children. --- ## Component Hierarchy Summary ``` tree (array of 3) └── Section ← exactly 3; fixed positions └── Container ← direct child of Section only; no nesting └── Column / Row ← freely nestable └── Text / Image / Button / Heading / Icon / Divider / Spacer ``` | Component | Allowed parent | Can nest itself? | Children | |---|---|---|---| | `Section` | `tree` (root array) | ✗ No | `Container` only | | `Container` | `Section` only | ✗ No | `Column`, `Row`, leaf nodes | | `Column` | `Container`, `Column`, `Row` | ✓ Yes | `Column`, `Row`, leaf nodes | | `Row` | `Container`, `Column`, `Row` | ✓ Yes | `Column`, `Row`, leaf nodes | | Leaf nodes | `Column`, `Row`, `Container` | ✗ No | None | --- ## The Three Sections ### `header` - Always the first Section in `tree` (index 0). - Intended for: logo, brand bar, navigation, pre-header text. - Contains one or more `Container` children stacked vertically. ### `content` - Always the second Section in `tree` (index 1). - Intended for: all email body content — hero, product rows, articles, CTAs, dividers. - Multiple `Container` children stack vertically to build up the content area. - This is where the bulk of the email lives. ### `footer` - Always the third Section in `tree` (index 2). - Intended for: unsubscribe link, legal text, social icons, mailing address. - Contains one or more `Container` children stacked vertically. --- ## Top-Level Fields ### `bodyStyle` Controls the email canvas appearance. All properties are optional. | Property | Type | Description | |---|---|---| | `color` | `string` | Default text color | | `fontSize` | `string` | Default font size | | `backgroundColor` | `string` | Background color of the email canvas | | `lineHeight` | `string` | Default line height | | `fontFamily` | `string` | Default font family | | `backgroundImage.src` | `string` | Background image URL | | `backgroundImage.repeat` | `string` | CSS `background-repeat` value | | `backgroundImage.size` | `string` | CSS `background-size` value | | `backgroundImage.position` | `string` | CSS `background-position` value | ### `dataLayer` An optional plain JSON object passed to the renderer at render time. It is the single source of truth for all dynamic values used by `bindings`. See **SECTION: DATA-BINDING** for the full reference. Omit or set to `{}` when the template has no dynamic content. ### `tree` An array of exactly three `Section` nodes in fixed order: `header`, `content`, `footer`. This is the entire component tree of the email body. Each `Section` holds `Container` children which in turn hold the layout and leaf components. --- ## Rules & Constraints - **One document = one flat object.** There is no `Html`, `Head`, or `Body` node in the document — those are internal rendering details handled by the builder. - **`tree` has exactly three items.** The three Sections are the only entries in `tree`. They cannot be added, removed, or reordered. - **Sections are immovable.** Their `sectionType` values and order are fixed: index 0 = `header`, index 1 = `content`, index 2 = `footer`. - **Containers do not nest.** Any container-inside-container structure is invalid regardless of depth. - **`Column` and `Row` are the only freely nestable structural components.** Use them to build any layout complexity below the Container level. - **Leaf nodes are terminal.** They cannot contain other components. - **`bodyStyle` is not a `ComponentNode`.** It is a plain config object, not a node with `id`, `type`, or `children`. --- SECTION: DATA-BINDING --- # Data Binding — LLM Reference Data binding connects a `ComponentNode` to the **Data Layer** — a structured JSON object provided at render time. It enables dynamic content, conditional visibility, and repeating components without duplicating template structure. --- ## The `bindings` Object Any `ComponentNode` from `Section` downward can carry a `bindings` property at the root of the node, alongside `id`, `type`, and `config`. ```json { "id": "product-image", "type": "image", "bindings": { "dataList": "products", "itemAlias": "product", "visible": "user.isPremium === true", "propertyMap": { "config.src": "product.image.src", "config.alt": "product.image.alt" } }, "config": { "src": "", "alt": "", "width": "100%" } } ``` > **`Body`, `Head`, and `Html` do not support bindings.** The binding system begins at `Section` level and applies to all descendants. --- ## `bindings` Properties ### `dataList` Makes the component a **repeater**. The component (and all its descendants) is rendered once per item in the referenced array. ```json "bindings": { "dataList": "job_listings", "itemAlias": "job" } ``` - Value is a dot-path to an array in the Data Layer (e.g. `"products"`, `"company.open_roles"`). - **Only components with structural children support `dataList`**: `Section`, `Container`, `Column`, `Row`. - Leaf nodes (`Text`, `Image`, `Button`, etc.) do not support `dataList` — place `dataList` on a parent `Container`, `Column`, or `Row` to repeat them. - `Section` supports `dataList` in principle, but since there are exactly 3 fixed Sections per template, using `dataList` there is rarely appropriate. Prefer `dataList` on a `Container` inside the `content` Section. --- ### `itemAlias` Names the current iteration item so descendant components can reference it in their own `propertyMap` and `visible` expressions. ```json "bindings": { "dataList": "products", "itemAlias": "product" } ``` - Required whenever `dataList` is set. - Descendant nodes reference the item using dot-notation: `"product.name"`, `"product.image.src"`, `"product.price"`. - The alias is scoped to the subtree of the repeating component — it is not accessible outside it. --- ### `visible` Controls conditional rendering. The component is hidden when the expression evaluates to falsy. ```json "bindings": { "visible": "user.isPremium === true" } ``` - Value is a JavaScript expression evaluated against the Data Layer. - Supported by all components from `Section` downward, including leaf nodes. - Can be combined with `dataList` to filter items: `"product.inStock === true"`. - When a component is hidden, it produces no HTML output — it does not occupy space. --- ### `propertyMap` Maps specific `config` properties of the component to Data Layer paths. This is the primary mechanism for injecting dynamic values into a component. ```json "bindings": { "propertyMap": { "config.src": "product.image.src", "config.alt": "product.image.alt", "config.backgroundColor": "product.brandColor" } } ``` **Key format:** Keys must be prefixed with `config.` followed by the exact property name as it appears in the component's `config` object. ``` "config.": "" ``` **Value format:** Values are dot-paths into the Data Layer or the current `itemAlias` scope. - Supported by all components from `Section` downward. - At render time, the resolved Data Layer value overwrites the `config` property. The value in `config` acts as a **fallback** when no Data Layer value is present. - `innerLink` and other complex nested objects cannot be targeted via `propertyMap` — only scalar `config` properties (strings, numbers, booleans). --- ## The Data Layer The Data Layer is a plain JSON object passed to the renderer at render time. It is the single source of truth for all dynamic values. ```json { "user": { "name": "Alex", "isPremium": true }, "products": [ { "name": "Wireless Headphones", "price": "$89", "brandColor": "#1d4ed8", "image": { "src": "https://cdn.example.com/headphones.jpg", "alt": "Wireless Headphones" }, "inStock": true }, { "name": "USB-C Hub", "price": "$45", "brandColor": "#065f46", "image": { "src": "https://cdn.example.com/hub.jpg", "alt": "USB-C Hub" }, "inStock": false } ] } ``` Dot-paths in `dataList`, `visible`, and `propertyMap` are resolved against this object. Nested paths are supported: `"user.name"`, `"product.image.src"`. --- ## Which Components Support What | Component | `dataList` | `itemAlias` | `visible` | `propertyMap` | |---|---|---|---|---| | `Html`, `Head`, `Body` | ✗ | ✗ | ✗ | ✗ | | `Section` | ✓ (rarely needed) | ✓ | ✓ | ✓ | | `Container` | ✓ | ✓ | ✓ | ✓ | | `Column` | ✓ | ✓ | ✓ | ✓ | | `Row` | ✓ | ✓ | ✓ | ✓ | | `Text`, `Heading`, `Button`, `Image`, `Icon`, `Divider`, `Spacer` | ✗ | ✗ | ✓ | ✓ | --- ## Common Patterns & Examples ### 1. Repeating a product card A `Container` iterates over a `products` array. Each child references the `product` alias. ```json { "id": "product-card", "type": "Container", "bindings": { "dataList": "products", "itemAlias": "product", "visible": "product.inStock === true" }, "config": { "widthType": "fixed", "width": "600px", "childrenConstraints": { "widthDistributionType": "equals" }, "padding": "16px" }, "children": [ { "id": "product-img", "type": "image", "bindings": { "propertyMap": { "config.src": "product.image.src", "config.alt": "product.image.alt" } }, "config": { "src": "", "alt": "", "width": "100%" } }, { "id": "product-name", "type": "Text", "bindings": { "propertyMap": { "config.text": "product.name" } }, "config": { "text": "Product name", "fontSize": "16px", "fontWeight": "700" } }, { "id": "product-price", "type": "Text", "bindings": { "propertyMap": { "config.text": "product.price" } }, "config": { "text": "$0.00", "fontSize": "14px", "color": "#6b7280" } } ] } ``` --- ### 2. Conditional section visibility Show a promotional band only when the user has an active promo. ```json { "id": "section-promo", "type": "Section", "bindings": { "visible": "user.hasActivePromo === true" }, "config": { "sectionType": "content", "backgroundColor": "#fef9c3", "padding": "0" }, "children": [] } ``` --- ### 3. Dynamic color via `propertyMap` Apply a brand color from the Data Layer to a Container background. ```json { "id": "brand-banner", "type": "Container", "bindings": { "propertyMap": { "config.backgroundColor": "brand.primaryColor" } }, "config": { "widthType": "full", "childrenConstraints": { "widthDistributionType": "equals" }, "backgroundColor": "#000000", "padding": "24px" }, "children": [] } ``` --- ### 4. Repeating a job listing in a `Column` ```json { "id": "jobs-column", "type": "Column", "bindings": { "dataList": "job_listings", "itemAlias": "job" }, "config": { "padding": "0", "gap": "16px" }, "children": [ { "id": "job-title", "type": "Heading", "bindings": { "propertyMap": { "config.text": "job.title" } }, "config": { "text": "Job Title", "level": "h3", "fontSize": "18px" } }, { "id": "job-location", "type": "Text", "bindings": { "propertyMap": { "config.text": "job.location" } }, "config": { "text": "Location", "fontSize": "14px", "color": "#6b7280" } } ] } ``` --- ## Rules & Constraints - **Binding scope starts at `Section`.** `Html`, `Head`, and `Body` do not participate in the binding system. - **`dataList` requires `itemAlias`.** Always provide both together. An `itemAlias` without a `dataList` has no effect. - **`dataList` is only valid on components with structural children.** Setting it on a leaf node is ignored. - **`propertyMap` keys must be prefixed with `config.`** The key `"config.text"` is correct; `"text"` is not. - **`propertyMap` targets scalar properties only.** Complex nested objects like `innerLink` or `backgroundImage` cannot be bound via `propertyMap`. - **`config` values are fallbacks.** If a Data Layer path resolves to `undefined` or `null`, the value in `config` is used instead. Always provide a sensible fallback in `config`. - **`visible` expressions are JavaScript.** They are evaluated as JS expressions against the Data Layer object. Use standard operators: `===`, `!==`, `&&`, `||`, `!`, `> `, `<`. - **Bindings do not cascade `itemAlias` beyond the repeater's subtree.** An `itemAlias` defined on a `Container` is not accessible in a sibling or parent component. --- SECTION: TEXT --- # Text Component — LLM Reference `Text` is the **primary typography primitive** in this email builder. It renders styled text content inside a table/TD structure for email client compatibility. It accepts either a raw HTML string (which has link styles injected automatically) or React node children. It supports the full range of CSS text properties, padding, background color, and an Outlook-safe `maxWidth` constraint. --- ## Position in the Document Tree ``` DocumentTree └── Section └── Container └── Column / Row └── Text ← leaf component, renders styled text ``` `Text` is a leaf node — it does not have structural children in the component tree. It sits at the end of the layout hierarchy inside a `Column`, `Row`, or directly inside a `Container` cell. --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "TextComponent", "config": { "text": "

Hello world

", "padding": "16px 24px", "color": "#111827", "textAlign": "left", "fontFamily": "Arial, sans-serif", "fontSize": "16px", "fontWeight": "400", "fontStyle": "normal", "lineHeight": "1.6", "letterSpacing": "0px", "textTransform": "none", "textDecoration": "none", "backgroundColor": "#ffffff", "opacity": 1, "whiteSpace": "normal", "wordBreak": "break-all", "maxWidth": "480px", "direction": "ltr" } } ``` --- ## `TextConfig` — Full Property Reference ### Content | Property | Type | Description | |---|---|---| | `text` | `string` | HTML string rendered via `dangerouslySetInnerHTML`. Link styles are automatically injected to match the text's color and decoration. If omitted, `children` (React nodes) are rendered instead. | ### Layout | Property | Type | Example | Description | |---|---|---|---| | `padding` | `string` | `"16px 24px"` | Applied to the containing ``. This is the only layout spacing on Text — there is no `margin`. | | `maxWidth` | `string` | `"480px"` | Constrains content width using an Outlook-safe `
` + table pattern. The outer `` stays at `width: 100%`. See **`maxWidth` behavior** below. | ### Typography | Property | Type | Example | Description | |---|---|---|---| | `color` | `string` | `"#111827"` | Text color | | `textAlign` | `"left" \| "center" \| "right" \| "justify"` | `"center"` | Horizontal text alignment. Also applied as the HTML `align` attribute on the `` for Outlook. | | `fontFamily` | `string` | `"Georgia, serif"` | Font stack. Use web-safe fonts or fonts declared in the email ``. | | `fontSize` | `string` | `"16px"` | Font size | | `fontWeight` | `string` | `"700"` | Font weight. Use numeric strings (`"400"`, `"700"`) or keywords (`"bold"`). | | `fontStyle` | `string` | `"italic"` | Font style | | `lineHeight` | `string \| number` | `"1.5"` or `"24px"` | Line height. Unitless values (e.g. `1.5`) are preferred for email compatibility. | | `letterSpacing` | `string` | `"0.5px"` | Letter spacing | | `textTransform` | `string` | `"uppercase"` | Text transform: `"uppercase"`, `"lowercase"`, `"capitalize"`, `"none"` | | `textDecoration` | `string` | `"underline"` | Text decoration: `"underline"`, `"line-through"`, `"none"` | | `direction` | `string` | `"rtl"` | Text direction: `"ltr"`, `"rtl"` | | `verticalAlign` | `string` | `"super"` | Vertical alignment within inline flow: `"sub"`, `"super"`, `"middle"`, etc. | | `whiteSpace` | `string` | `"nowrap"` | White-space handling: `"normal"`, `"nowrap"`, `"pre"`, `"pre-wrap"` | | `wordBreak` | `string` | `"break-word"` | Word break: `"break-all"`, `"break-word"`, `"keep-all"`, `"normal"`. **Defaults to `"break-all"`** if not set. | ### Visual | Property | Type | Example | Description | |---|---|---|---| | `backgroundColor` | `string` | `"#fef9c3"` | Background color of the text block's containing `` | | `opacity` | `string \| number` | `0.7` | Opacity of the inner content div | | `listStyle` | `string` | `"disc"` | List style (informational — consumed by builder UI, not directly applied as CSS by this component) | --- ## `text` vs `children` `Text` accepts content in two ways: **`config.text` (string)** — an HTML string rendered via `dangerouslySetInnerHTML`. Link styles from the text's `color`, `textDecoration`, etc. are automatically injected into `` tags inside the HTML via `injectLinkStyles`. This is the standard mode used by the builder. ```json "text": "

Visit our website for details.

" ``` **`children` (React nodes)** — rendered directly inside the styled `
`. Used when composing `Text` programmatically in React. `injectLinkStyles` is not applied in this mode. If both `config.text` and `children` are provided, `config.text` takes precedence. --- ## `maxWidth` Behavior `maxWidth` uses the same Outlook Classic compatibility pattern as `Column.maxWidth`: - The outer `` stays at `width: 100%` — it always fills its parent cell - Inside it, `
` handles horizontal centering in Outlook Classic (Word engine) - Inside `
`, an inner table with `width={maxWidth}` as an HTML attribute — Outlook Classic respects this as a hard pixel cap - Modern clients receive `max-width: {maxWidth}` as CSS on the same table > **Important:** `maxWidth` is **not** applied to the inner `
` — CSS `max-width` on a `
` is ignored by Outlook Classic. The constraint is enforced at the table level only. **When to use:** When a Text block sits in a wide column but you want to limit the line length for readability. For example, a 600px Section containing a single text column where lines should cap at 480px. ```json "config": { "maxWidth": "480px", "textAlign": "center" } ``` --- ## Link Style Injection When `config.text` is a string, `injectLinkStyles` automatically propagates the text block's styles to all `` tags in the HTML. This ensures links inherit the correct `color`, `textDecoration`, `fontFamily`, `fontSize`, etc. rather than defaulting to browser/email-client link styles. This means you do **not** need to manually style links in the HTML string — set the styles on the `TextConfig` and they will be applied to links automatically. ```json "config": { "text": "

Read more here.

", "color": "#6366f1", "textDecoration": "none" } ``` The `` tag will receive `color: #6366f1` and `text-decoration: none` automatically. --- ## `wordBreak` Default Unlike all other properties, `wordBreak` has a **non-`undefined` default**: it defaults to `"break-all"` when not specified. This prevents long URLs or unbroken strings from overflowing narrow email columns in most clients. Set `wordBreak: "normal"` explicitly if you need standard word-break behavior (e.g., for CJK text or controlled layouts where overflow is handled elsewhere). --- ## React Usage ```tsx import Text, { TextConfig } from './Text'; // String HTML content (standard) const config: TextConfig = { text: '

Hello world

', fontSize: '16px', color: '#111827', lineHeight: '1.6', padding: '16px 24px', }; // React node children (programmatic) Custom content ``` The component is memoized via `arePropsEqual`. Pass stable `config` objects to avoid re-renders. --- ## Common Patterns & Examples ### 1. Body paragraph ```json { "id": "body-text", "type": "TextComponent", "config": { "text": "

Your order has been confirmed and will ship within 2 business days.

", "fontSize": "16px", "fontFamily": "Arial, sans-serif", "color": "#374151", "lineHeight": "1.6", "padding": "0 0 16px 0" } } ``` --- ### 2. Section heading ```json { "id": "heading", "type": "TextComponent", "config": { "text": "

Welcome back, Alex

", "fontSize": "28px", "fontWeight": "700", "color": "#111827", "lineHeight": "1.2", "textAlign": "center", "padding": "0 0 8px 0" } } ``` --- ### 3. Centered subheading with letter spacing ```json { "id": "eyebrow", "type": "TextComponent", "config": { "text": "

NEW COLLECTION

", "fontSize": "12px", "fontWeight": "600", "color": "#6366f1", "textTransform": "uppercase", "letterSpacing": "2px", "textAlign": "center", "lineHeight": "1" } } ``` --- ### 4. Footer legal text (muted, small) ```json { "id": "footer-legal", "type": "TextComponent", "config": { "text": "

You received this email because you signed up at example.com. Unsubscribe

", "fontSize": "12px", "color": "#9ca3af", "textAlign": "center", "lineHeight": "1.5", "padding": "16px 24px" } } ``` The unsubscribe link will automatically inherit `color: #9ca3af` via link style injection. --- ### 5. RTL text block ```json { "id": "rtl-text", "type": "TextComponent", "config": { "text": "

مرحباً بك في متجرنا

", "direction": "rtl", "textAlign": "right", "fontFamily": "Arial, sans-serif", "fontSize": "16px", "color": "#111827", "lineHeight": "1.8" } } ``` --- ### 6. Highlighted callout text with background ```json { "id": "highlight-text", "type": "TextComponent", "config": { "text": "

Limited time offer: Free shipping on all orders over $50.

", "fontSize": "15px", "color": "#713f12", "backgroundColor": "#fef9c3", "padding": "12px 16px", "lineHeight": "1.5", "wordBreak": "break-word" } } ``` --- ### 7. Constrained reading-width text ```json { "id": "article-body", "type": "TextComponent", "config": { "text": "

Long-form article content goes here...

", "maxWidth": "480px", "textAlign": "left", "fontSize": "16px", "color": "#374151", "lineHeight": "1.7", "padding": "0" } } ``` --- ## Data Bindings `Text` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Leaf components do not support `dataList` or `itemAlias` — use those on a parent `Container`, `Column`, or `Row` to repeat the component. `visible` and `propertyMap` are fully supported. ```json { "id": "promo-text", "type": "TextComponent", "bindings": { "visible": "user.isPremium === true", "propertyMap": { "config.text": "promo.body", "config.color": "promo.textColor" } }, "config": { "text": "

Fallback text

", "fontSize": "16px", "color": "#111827" } } ``` | Property | Description | |---|---| | `visible` | JS expression evaluated against the Data Layer. Hides this component when falsy. | | `propertyMap` | Maps component property paths (prefixed with `config.`) to Data Layer paths. | > Bindable properties include `config.text`, `config.color`, `config.backgroundColor`, `config.fontSize`, `config.fontFamily`, `config.padding`, and any other scalar `TextConfig` field. --- ## Rules & Constraints - **`wordBreak` defaults to `"break-all"`** unless explicitly set. This is the only property with a non-`undefined` default. Set `"normal"` to opt out. - **`maxWidth` is enforced at the table level, not on the `
`.** Do not attempt to replicate this behavior using CSS on inner elements — Outlook Classic will ignore it. - **`padding` is the only spacing mechanism.** There is no `margin` on `Text`. Use `padding` on this component or `gap` on the parent `Column` for spacing between text blocks. - **`backgroundColor` applies to the outer ``, not the `
`.** It covers the full padded area including the padding space. - **`text` takes precedence over `children`** when both are supplied. - **Link styles are only injected when `text` is a string.** React node `children` receive no automatic link styling — style links manually in that case. - **`listStyle` is informational.** It is stored in config and consumed by the builder UI, but is not applied as CSS by this component's renderer. - **`opacity` applies to the inner `
`**, not the ``. This means `backgroundColor` (on the ``) is not affected by `opacity` — the background remains fully opaque even when `opacity < 1`. - **`textAlign` is applied both as CSS and as the HTML `align` attribute** on the `` for Outlook Classic compatibility. No special handling needed — set `textAlign` normally. --- SECTION: SPACER --- # Spacer Component — LLM Reference `Spacer` is a **vertical whitespace primitive**. It renders a fixed-height transparent block that creates consistent vertical spacing between components in an email. It has no visual output beyond the space it occupies — no background, no border, no text. It is the correct tool for adding vertical rhythm between layout elements. --- ## Position in the Document Tree ``` DocumentTree └── Section └── Container └── Column └── Heading (Text) └── Spacer ← vertical gap between elements └── Text └── Spacer ← vertical gap before button └── Button ``` `Spacer` is a leaf node. It can be placed anywhere a child component is accepted: inside `Column`, `Row`, directly in a `Container` cell, or between any sibling components. --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "SpacerComponent", "config": { "height": "24px", "hideOnMobile": false } } ``` --- ## `SpacerConfig` — Full Property Reference | Property | Type | Required | Default | Description | |---|---|---|---|---| | `height` | `string` | ✓ Yes | — | Height of the vertical space. Must be a px value (e.g. `"16px"`, `"48px"`). | | `hideOnMobile` | `boolean` | No | `false` | When `true`, adds the `hide-on-mobile` CSS class to the outer table. The builder's global stylesheet collapses the spacer on mobile viewports. | --- ## How It Works `Spacer` creates vertical space using three reinforcing techniques for cross-client reliability: 1. **CSS `height`** on the `` — respected by modern email clients 2. **HTML `height` attribute** on both the `` and the `
` — required for Outlook Classic (Word rendering engine), which ignores CSS height on table cells 3. **`fontSize: "0"` and `lineHeight: "0"`** on the `` — suppresses any phantom vertical space that font metrics would otherwise add, even with no visible text content 4. **` `** inside the `` — ensures the cell renders in clients that collapse empty cells The outer table uses `width: 100%` so the spacer spans the full width of its parent column, maintaining layout integrity. --- ## `height` Format `height` must be a **pixel string** (e.g. `"24px"`). The numeric value is parsed and applied as both the CSS `height` and the HTML `height` attribute. Non-px values (e.g. `"1.5rem"`, `"10%"`) will have their integer portion extracted via `parseInt` — the `rem`/`%` unit will be silently dropped and the raw integer used as pixels. Always use `px` units for predictable behavior. ```json // Correct "height": "32px" // Avoid — unit stripped, only "10" used "height": "10%" ``` --- ## `hideOnMobile` When `hideOnMobile: true`, the outer table receives the `hide-on-mobile` CSS class. The builder's global `` stylesheet sets `display: none` on this class for mobile viewports. This allows a large desktop spacer to be hidden on mobile without a second Spacer component. ```json // Large desktop spacer, hidden on mobile { "id": "desktop-spacer", "type": "SpacerComponent", "config": { "height": "48px", "hideOnMobile": true } } // Paired small mobile spacer (always visible) { "id": "mobile-spacer", "type": "SpacerComponent", "config": { "height": "16px" } } ``` --- ## React Usage ```tsx import Spacer, { SpacerConfig } from './Spacer'; const config: SpacerConfig = { height: '24px', }; ``` The component is memoized via `arePropsEqual`. Pass a stable `config` reference to avoid re-renders. --- ## Spacer vs `gap` on Parent Components Both `Spacer` and parent `gap` properties create vertical space, but they serve different purposes: | | `Spacer` | `gap` on `Column` | |---|---|---| | **Mechanism** | Explicit child node in the tree | Config property on the parent | | **Granularity** | Per-gap, any size | Uniform between all children | | **Mobile control** | `hideOnMobile` per spacer | Single value for all gaps | | **Outlook support** | ✓ Full (HTML attribute + CSS) | ✓ Full (spacer `` row) | | **When to use** | Different spacing between specific pairs of children, or mobile-responsive spacing | Uniform spacing between all children in a column | Use `gap` on `Column` when all children should be evenly spaced. Use `Spacer` when you need variable spacing between specific elements, or when you need mobile-specific control over individual gaps. --- ## Common Patterns & Examples ### 1. Standard content gap ```json { "id": "spacer-16", "type": "SpacerComponent", "config": { "height": "16px" } } ``` --- ### 2. Section breathing room (top/bottom of a column) ```json { "id": "spacer-top", "type": "SpacerComponent", "config": { "height": "40px" } } ``` --- ### 3. Large desktop spacer, hidden on mobile ```json { "id": "spacer-hero-gap", "type": "SpacerComponent", "config": { "height": "64px", "hideOnMobile": true } } ``` --- ### 4. Spacer between heading and body text ```json [ { "id": "heading", "type": "TextComponent", "config": { "text": "

Our latest products

", "fontSize": "24px", "fontWeight": "700" } }, { "id": "spacer-heading-body", "type": "SpacerComponent", "config": { "height": "12px" } }, { "id": "body", "type": "TextComponent", "config": { "text": "

Discover our new arrivals this season.

", "fontSize": "16px" } } ] ``` --- ## Data Bindings `Spacer` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Leaf components do not support `dataList` or `itemAlias` — use those on a parent `Container`, `Column`, or `Row` to repeat the component. `visible` and `propertyMap` are fully supported. ```json { "id": "dynamic-spacer", "type": "SpacerComponent", "bindings": { "visible": "layout.showSpacing === true" }, "config": { "height": "32px" } } ``` | Property | Description | |---|---| | `visible` | JS expression evaluated against the Data Layer. Hides this component when falsy. | | `propertyMap` | Maps component property paths (prefixed with `config.`) to Data Layer paths. | > `Spacer` has no meaningful `propertyMap` targets beyond `config.height`. Conditional rendering via `visible` is the primary binding use case. --- ## Rules & Constraints - **`height` is required.** There is no default — omitting it will cause a broken render. Always provide a `px` string value. - **Use `px` units only.** Non-px values are silently truncated to their integer portion and treated as pixels. `"1.5rem"` becomes `1`, `"10%"` becomes `10` — both likely unintended. - **`Spacer` has no visual properties.** It cannot have a background color, border, or content. It is transparent by design. To create a visible divider, use a `Container` or `Row` with a `border` instead. - **Do not use `Spacer` for horizontal spacing.** It always renders at `width: 100%`. Horizontal gaps between siblings are handled by the `gap` property on `Container`, `Column`, or `Row`. - **`hideOnMobile` requires the global stylesheet.** The `hide-on-mobile` class has no effect without the builder's `` CSS. In isolated rendering contexts, the spacer will always be visible regardless of this flag. - **Minimum effective height is `1px`.** `parseInt` falls back to `1` if the height string produces `0` or `NaN`. A `"0px"` spacer renders with `height: 1` in Outlook — use the `Column` or `Row` `gap` property instead if you need truly zero space. --- SECTION: SECTION --- # Section Component — LLM Reference `Section` is the **top-level structural wrapper** for an email. It renders as a full-width `` that spans the entire email width and acts as the outermost shell for a band of content. A template contains **exactly three Sections** — `header`, `content`, and `footer` — in fixed positions. They cannot be added, removed, reordered, or nested. Sections are **not** layout containers — they don't distribute children side-by-side. That is the job of `Container`. A Section holds one or more `Container` components stacked vertically as children, and those Containers handle column logic. --- ## Position in the Document Tree ``` Body (ComponentNode) └── children (Array) └── Section ← Full-width band └── Container ← Layout & width control └── Column ← Vertical stacking ``` A `Section` is always a **root-level node** in the `DocumentTree`. There are exactly three — `header`, `content`, `footer` — and their positions are fixed. They are never nested inside each other or inside a `Container`. --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "SectionComponent", "config": { "sectionType": "content", "backgroundColor": "#f3f4f6", "padding": "24px 0", "gap": "16px", "border": { "bottom": { "width": "1px", "style": "solid", "color": "#e5e7eb" } }, "backgroundImage": { "src": "https://example.com/bg.jpg", "repeat": "no-repeat", "size": "cover", "position": "center center" } }, "children": [] } ``` --- ## `SectionConfig` — Full Property Reference ### Required | Property | Type | Options | Description | |---|---|---|---| | `sectionType` | `string` | `"header"`, `"footer"`, `"content"` | Semantic role of the section. Used for labeling and dev tooling — does not affect visual rendering. | ### Layout | Property | Type | Example | Description | |---|---|---|---| | `padding` | `string` | `"48px 0"` | Inner padding applied to the single `
` that wraps all children. CSS shorthand. | | `gap` | `string` | `"16px"` | Vertical space between children (informational — consumed by the builder's layout engine, not directly rendered by this component). | ### Visual | Property | Type | Example | Description | |---|---|---|---| | `backgroundColor` | `string` | `"#1e1b4b"` | Background fill color for the full-width band | | `border` | `BorderConfig` | See below | Border applied to the outer `` element | | `backgroundImage` | `object` | See below | Background image for the section band | ### `BorderConfig` Specify either a **full border** (all sides) or **individual sides**. Do not mix both in the same object. ```json // Full border — all sides "border": { "width": "1px", "style": "solid", "color": "#e5e7eb" } // Per-side border "border": { "top": { "width": "3px", "style": "solid", "color": "#6366f1" } } ``` Supported side keys: `"top"`, `"right"`, `"bottom"`, `"left"`. Each side object requires all three of: `width`, `style`, `color`. When individual sides are specified, all other sides are explicitly set to `"none"` to prevent Outlook from rendering phantom black borders. ### `backgroundImage` ```json "backgroundImage": { "src": "https://example.com/texture.jpg", "repeat": "no-repeat", "size": "cover", "position": "center center" } ``` | Property | Type | Description | |---|---|---| | `src` | `string` | Absolute URL of the image | | `repeat` | `string` | Any CSS `background-repeat` value: `"no-repeat"`, `"repeat"`, `"repeat-x"`, `"repeat-y"` | | `size` | `string` | Any CSS `background-size` value: `"cover"`, `"contain"`, `"auto"` | | `position` | `string` | Any CSS `background-position` value: `"center center"`, `"top left"`, etc. | --- ## Section vs Container — Key Differences | | `Section` | `Container` | |---|---|---| | **Purpose** | Full-width email band | Multi-column layout | | **Width** | Always 100% | `"full"` or `"fixed"` (e.g. `"600px"`) | | **Column layout** | ✗ No | ✓ Yes (`childrenConstraints`) | | **Position in tree** | Root level only | Inside a Section | | **Stacking direction** | Vertical (Sections stack top-to-bottom) | Horizontal (children placed left-to-right) | | **Background image** | ✓ Yes | ✓ Yes | | **Border** | ✓ Yes | ✓ Yes | | **Border radius** | ✗ No | ✓ Yes | | **`alignItems`** | ✗ No | ✓ Yes | | **`justifyContent`** | ✗ No | ✓ Yes | --- ## React Usage ```tsx import Section, { SectionConfig } from './Section'; const config: SectionConfig = { sectionType: 'content', backgroundColor: '#f9fafb', padding: '32px 0', };
``` The component is memoized via `arePropsEqual`. Pass stable `config` references (e.g. with `useMemo`) to avoid unnecessary re-renders. --- ## `sectionType` Semantics `sectionType` is a semantic label — it has no effect on rendered styles but is used by the builder's dev overlay and accessibility labels. | Value | Intended use | |---|---| | `"header"` | Logo bar, navigation, pre-header text | | `"content"` | Body rows — hero, product blocks, articles, CTAs | | `"footer"` | Unsubscribe links, legal text, social icons, address | Each value maps to exactly one Section in the template. There is one `"header"`, one `"content"`, and one `"footer"` — never more. --- ## Common Patterns & Examples ### 1. Minimal content section ```json { "id": "section-body", "type": "SectionComponent", "config": { "sectionType": "content", "backgroundColor": "#ffffff", "padding": "0" }, "children": [] } ``` --- ### 2. Header section with brand background ```json { "id": "section-header", "type": "SectionComponent", "config": { "sectionType": "header", "backgroundColor": "#1e1b4b", "padding": "16px 0" }, "children": [ { "id": "header-container", "type": "ContainerComponent", "config": { "widthType": "fixed", "width": "600px", "childrenConstraints": { "widthDistributionType": "equals" }, "justifyContent": "center", "padding": "0 24px" }, "children": [] } ] } ``` --- ### 3. Footer section with top border divider ```json { "id": "section-footer", "type": "SectionComponent", "config": { "sectionType": "footer", "backgroundColor": "#f9fafb", "padding": "24px 0", "border": { "top": { "width": "1px", "style": "solid", "color": "#e5e7eb" } } }, "children": [] } ``` --- ### 4. Hero section with full-bleed background image ```json { "id": "section-hero", "type": "SectionComponent", "config": { "sectionType": "content", "backgroundColor": "#000000", "padding": "0", "backgroundImage": { "src": "https://example.com/hero.jpg", "repeat": "no-repeat", "size": "cover", "position": "center center" } }, "children": [ { "id": "hero-container", "type": "ContainerComponent", "config": { "widthType": "fixed", "width": "600px", "childrenConstraints": { "widthDistributionType": "equals" }, "justifyContent": "center", "alignItems": "center", "padding": "80px 40px" }, "children": [] } ] } ``` --- ### 5. Striped layout using multiple Containers Since a template has exactly one `content` Section, alternating background rows are achieved with multiple `Container` children stacked inside it — not multiple Sections. ```json { "id": "section-content", "type": "SectionComponent", "config": { "sectionType": "content", "backgroundColor": "#ffffff", "padding": "0" }, "children": [ { "id": "row-1", "type": "ContainerComponent", "config": { "widthType": "full", "childrenConstraints": { "widthDistributionType": "equals" }, "backgroundColor": "#ffffff", "padding": "32px 24px" }, "children": [] }, { "id": "row-2", "type": "ContainerComponent", "config": { "widthType": "full", "childrenConstraints": { "widthDistributionType": "equals" }, "backgroundColor": "#f3f4f6", "padding": "32px 24px" }, "children": [] }, { "id": "row-3", "type": "ContainerComponent", "config": { "widthType": "full", "childrenConstraints": { "widthDistributionType": "equals" }, "backgroundColor": "#ffffff", "padding": "32px 24px" }, "children": [] } ] } ``` --- ### 6. Section with data binding (conditional visibility) ```json { "id": "section-promo", "type": "SectionComponent", "bindings": { "visible": "user.hasActivePromo === true" }, "config": { "sectionType": "content", "backgroundColor": "#fef9c3", "padding": "16px 0", "border": { "bottom": { "width": "2px", "style": "solid", "color": "#facc15" } } }, "children": [] } ``` --- ## Data Bindings `Section` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Bindings connect this component to the Data Layer for dynamic rendering. ```json { "id": "section-promo", "type": "SectionComponent", "bindings": { "visible": "user.hasActivePromo === true", "propertyMap": { "config.backgroundColor": "promo.bgColor" } }, "config": { "sectionType": "content", "backgroundColor": "#fef9c3", "padding": "16px 0" }, "children": [] } ``` | Property | Description | |---|---| | `dataList` | Not applicable. Sections are fixed structural slots — they cannot be repeated. Use `dataList` on a `Container` child instead. | | `itemAlias` | Not applicable for the same reason — use on `Container`. | | `visible` | JS expression evaluated against the Data Layer. Hides the entire section band when falsy. | | `propertyMap` | Maps `config` property paths to Data Layer paths (e.g. `"config.backgroundColor"` → `"promo.bgColor"`). | --- ## Rules & Constraints - **Exactly three, fixed positions.** A template always has exactly one `header`, one `content`, and one `footer` Section. They cannot be added, removed, reordered, or nested inside any other component. - **One `
` only.** The Section renders a single cell. All width constraint and column logic must live inside a `Container` child. - **`padding` applies to the inner cell**, not the outer table. For full-bleed background colors or images, set `padding: "0"` on the Section and add padding inside the child Container instead. - **`gap` is informational.** The `gap` property on Section is stored in config but is consumed by the builder's drag-and-drop engine for spacing between Sections — it is not applied as CSS by this component. - **`border` renders on the outer ``.** Unlike `Container`, there is no border-radius support on Section — borders will always be square-cornered. - **Background image layering.** `backgroundColor` acts as a fallback when the background image fails to load. Always set a contrasting `backgroundColor` alongside any `backgroundImage`. - **`sectionType` does not affect styles.** It is only a semantic label for dev tooling and accessibility. Any `sectionType` value can use any visual config. --- SECTION: ROW --- # Row Component — LLM Reference `Row` is a **horizontal arrangement primitive** that sits inside a `Container` column. It places its children side-by-side in a single ``, handles gap columns between them, and supports alignment, padding, background styling, borders, links, and mobile stacking. It does **not** control email-level width — that is the job of `Container`. --- ## Position in the Document Tree ``` DocumentTree └── Section ← full-width band └── Container ← column layout & email width └── Row ← horizontal child arrangement └── Image / Text / Button / ... ``` A `Row` lives inside a `Container` (or another component that acts as a column). Unlike `Container`, a `Row` does not manage fixed vs. full width at the email level — it fills whatever space its parent column provides. --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "RowComponent", "config": { "gap": "16px", "justifyContent": "center", "alignItems": "center", "fillWidth": false, "layoutColumns": "equal", "padding": "12px", "backgroundColor": "#ffffff", "borderRadius": "8px", "border": { "width": "1px", "style": "solid", "color": "#e5e7eb" }, "backgroundImage": { "src": "https://example.com/bg.jpg", "repeat": "no-repeat", "size": "cover", "position": "center center" }, "width": "100%", "height": "64px", "innerLink": { "type": "url", "url": "https://example.com", "target": "_blank" }, "mobile": { "wrap": true, "justifyContent": "center", "alignItems": "start" } }, "children": [] } ``` --- ## `RowConfig` — Full Property Reference ### Layout | Property | Type | Example | Description | |---|---|---|---| | `gap` | `string` | `"16px"` | Horizontal space between children. Rendered as a gap `
`. Also used as vertical gap between stacked children on mobile. | | `justifyContent` | `"start" \| "center" \| "end"` | `"center"` | Horizontal alignment of children within the row | | `alignItems` | `"start" \| "center" \| "end"` | `"center"` | Vertical alignment of children within the row | | `width` | `string` | `"100%"` | Width of the outer row table. Defaults to `100%`. | | `height` | `string` | `"80px"` | Fixed height applied to all table levels | | `fillWidth` | `boolean` | `true` | See **`fillWidth` vs `layoutColumns`** section below | | `layoutColumns` | `"equal" \| string[]` | `"equal"` | Controls per-child `` width distribution. See below. | ### Visual | Property | Type | Example | Description | |---|---|---|---| | `padding` | `string` | `"16px 24px"` | Inner padding applied inside the border wrapper | | `backgroundColor` | `string` | `"#f9fafb"` | Background fill color | | `borderRadius` | `string` | `"8px"` | Rounds all corners (also clips background) | | `border` | `BorderConfig` | See below | Border on the outer wrapper table | | `backgroundImage` | `BackgroundImageType` | See below | Background image for the row | ### Interactivity | Property | Type | Description | |---|---|---| | `innerLink` | `IInnerLink` | Wraps the entire row in an `` tag. See **Link Support** section. | ### Mobile | Property | Type | Description | |---|---|---| | `mobile.wrap` | `boolean` | When `true`, children stack vertically on mobile. Gap is injected as a spacer between stacked children. | | `mobile.justifyContent` | `"start" \| "center" \| "end"` | Overrides `justifyContent` on mobile | | `mobile.alignItems` | `"start" \| "center" \| "end"` | Overrides `alignItems` on mobile | --- ## `fillWidth` vs `layoutColumns` — Critical Distinction These two properties both control how the content table expands, but they serve different purposes and can be combined. ### `fillWidth` Controls whether the inner content table uses `width: 100%` or `width: auto`. | `fillWidth` | Content table width | Use case | |---|---|---| | `false` / `undefined` (default) | `width: auto` | Children shrink-wrap to natural size. Use for icon rows, button rows, social links. | | `true` | `width: 100%` | Children get a constrained box — text can wrap correctly in Outlook. Use for rows mixing text + images. | ### `layoutColumns` Controls how each child `` is sized. When set, also forces `table-layout: fixed` and `width: 100%` on the content table (regardless of `fillWidth`). | Value | Behavior | |---|---| | `undefined` (default) | No width is set on child ``s — original shrink-wrap behavior | | `"equal"` | Each child gets `${100 / numChildren}%`. Works for any number of children. | | `string[]` | Explicit width per child (px, %, or mixed). Array **must** match children count exactly. | ```json // Equal: 3 children each get 33.33% "layoutColumns": "equal" // Manual: explicit per-child widths "layoutColumns": ["200px", "1fr", "80px"] // Mixed units are allowed — author is responsible for not overflowing "layoutColumns": ["60%", "40%"] ``` > **Note:** When using `%` values in `layoutColumns`, ensure they total ≤ 100% after accounting for any `gap`. Overflow behavior is undefined and client-dependent — the same trade-off accepted by MJML and Foundation for Emails. ### Decision Guide | Scenario | `fillWidth` | `layoutColumns` | |---|---|---| | Icon row, button row, social links | `false` | `undefined` | | Text + image side by side | `true` | `undefined` | | Equal-width columns (e.g. 3 product cards) | either | `"equal"` | | Asymmetric columns (e.g. 70/30 split) | either | `["70%", "30%"]` | | Fixed sidebar + fluid content | either | `["200px", "auto"]` | --- ## `BorderConfig` Specify either a **full border** (all sides) or **individual sides**. Do not mix both. ```json // Full border — all sides "border": { "width": "1px", "style": "solid", "color": "#e5e7eb" } // Per-side — other sides are explicitly set to "none" (Outlook-safe) "border": { "left": { "width": "4px", "style": "solid", "color": "#6366f1" } } ``` Supported side keys: `"top"`, `"right"`, `"bottom"`, `"left"`. Each side requires all three of: `width`, `style`, `color`. --- ## `backgroundImage` ```json "backgroundImage": { "src": "https://example.com/texture.jpg", "repeat": "no-repeat", "size": "cover", "position": "center center" } ``` | Property | Options | |---|---| | `repeat` | `"no-repeat"`, `"repeat"`, `"repeat-x"`, `"repeat-y"` | | `size` | `"auto"`, `"cover"`, `"contain"` | | `position` | Any valid CSS `background-position` string | Always pair with `backgroundColor` as a fallback for email clients that block images. --- ## Link Support (`innerLink`) When `innerLink` is set (and not `devMode`), the entire row is wrapped in an `` tag with `display: block`. The link is stripped in dev mode. ```json "innerLink": { "type": "url", "url": "https://example.com", "target": "_blank" } ``` | `type` | Required field | Rendered href | |---|---|---| | `"url"` | `url` | The URL as-is | | `"email"` | `email` | `mailto:{email}` | | `"phone"` | `phone` | `tel:{phone}` | | `"anchor"` | `anchor` | `#{anchor}` | | `"page_top"` | — | `#` | | `"page_bottom"` | — | `#bottom` | | `"none"` | — | No link rendered | `target` defaults to `"_blank"` if not specified. --- ## Mobile Stacking When `mobile.wrap: true` and there are 2+ children, the Row uses the same CSS-class-based stacking pattern as `Container` — compatible with Gmail's `@media` stripping. - Each child `` gets the `stack-td` class → forces `display: block; width: 100%` on mobile - Gap columns get `desktop-gap-column` → collapsed on mobile - A `mobile-gap-spacer` div is injected inside each child (except the last) → visible only on mobile, provides vertical spacing equal to `gap` ```json "config": { "gap": "16px", "mobile": { "wrap": true } } ``` > **Important:** `mobile.wrap` only activates stacking behavior. The `gap` value controls both horizontal gap (desktop) and vertical gap (mobile stack). If you want different spacing per breakpoint, that is not currently supported — one `gap` value serves both. --- ## React Usage ```tsx import Row, { RowConfig } from './Row'; const config: RowConfig = { gap: '16px', justifyContent: 'center', alignItems: 'center', fillWidth: false, }; ``` The component is memoized via `arePropsEqual`. Pass stable `config` objects to avoid re-renders. --- ## Row vs Container — Key Differences | | `Row` | `Container` | |---|---|---| | **Primary job** | Arrange children horizontally with alignment + gap | Control email column layout and fixed vs. full width | | **Width control** | Fills parent column (`width: 100%` default) | `"fixed"` (e.g. `600px`, centered) or `"full"` | | **Column sizing** | `layoutColumns` (`"equal"` or `string[]`) | `childrenConstraints` (`"equals"`, `"ratio"`, `"manual"`) | | **`fillWidth`** | ✓ Yes — shrink-wrap vs. full-width content table | ✗ No | | **Link wrapping** | ✓ Yes — `innerLink` wraps the whole row in `` | ✗ No | | **Mobile stacking** | ✓ Yes — `mobile.wrap` | ✓ Yes — `shouldWrap` | | **Border radius** | ✓ Yes | ✓ Yes | | **Background image** | ✓ Yes | ✓ Yes | | **`justifyContent`** | ✓ Yes — aligns children within the row | ✓ Yes — aligns the container table within its parent | --- ## Common Patterns & Examples ### 1. Icon/social link row (default shrink-wrap) ```json { "id": "social-row", "type": "RowComponent", "config": { "gap": "12px", "justifyContent": "center", "alignItems": "center" }, "children": [] } ``` No `fillWidth`, no `layoutColumns` — children naturally size to their content. --- ### 2. Text + image side by side (fillWidth for Outlook wrapping) ```json { "id": "text-image-row", "type": "RowComponent", "config": { "gap": "24px", "alignItems": "center", "fillWidth": true, "layoutColumns": ["60%", "40%"], "mobile": { "wrap": true } }, "children": [] } ``` --- ### 3. Equal-width product cards (3-up) ```json { "id": "product-row", "type": "RowComponent", "config": { "gap": "16px", "justifyContent": "center", "layoutColumns": "equal", "mobile": { "wrap": true } }, "children": [] } ``` --- ### 4. Single centered button ```json { "id": "cta-row", "type": "RowComponent", "config": { "justifyContent": "center", "padding": "8px 0" }, "children": [] } ``` No gap needed for a single child. No `layoutColumns` — button sizes to its natural width. --- ### 5. Clickable row (entire row is a link) ```json { "id": "link-row", "type": "RowComponent", "config": { "gap": "12px", "alignItems": "center", "fillWidth": true, "backgroundColor": "#f3f4f6", "padding": "16px", "borderRadius": "8px", "innerLink": { "type": "url", "url": "https://example.com/article", "target": "_blank" } }, "children": [] } ``` --- ### 6. Accent left-border row (callout style) ```json { "id": "callout-row", "type": "RowComponent", "config": { "fillWidth": true, "padding": "16px 20px", "backgroundColor": "#f0f9ff", "border": { "left": { "width": "4px", "style": "solid", "color": "#0ea5e9" } } }, "children": [] } ``` --- ### 7. Fixed-height banner row with background image ```json { "id": "banner-row", "type": "RowComponent", "config": { "height": "200px", "justifyContent": "center", "alignItems": "center", "backgroundColor": "#0f172a", "backgroundImage": { "src": "https://example.com/banner.jpg", "repeat": "no-repeat", "size": "cover", "position": "center center" } }, "children": [] } ``` --- ## Data Bindings `Row` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Bindings connect this component to the Data Layer for dynamic rendering. ```json { "id": "social-row", "type": "RowComponent", "bindings": { "dataList": "social_links", "itemAlias": "link", "visible": "user.showSocial === true", "propertyMap": { "config.backgroundColor": "link.brandColor" } }, "config": { "gap": "12px", "justifyContent": "center", "alignItems": "center" }, "children": [] } ``` | Property | Description | |---|---| | `dataList` | Dot-path to an array in the Data Layer. Makes `Row` a repeater — one row rendered per item. | | `itemAlias` | Name used by descendant components to reference the current iteration item (e.g. `"link"` → `"link.brandColor"`). | | `visible` | JS expression evaluated against the Data Layer. Hides the entire row when falsy. | | `propertyMap` | Maps `config` property paths to Data Layer paths (e.g. `"config.backgroundColor"` → `"link.brandColor"`). | --- ## Rules & Constraints - **`layoutColumns` array must match children count exactly.** If lengths differ, widths are silently not applied (undefined fallback). - **`layoutColumns` forces `table-layout: fixed` and `width: 100%`** on the content table, overriding `fillWidth`. You cannot have `layoutColumns` without a full-width content table. - **`gap` doubles as mobile spacer height** when `mobile.wrap: true`. A single value serves both orientations. - **`innerLink` is suppressed in `devMode`.** The row renders without the `` wrapper when dev mode is active. - **`borderRadius` clips backgrounds.** Setting `borderRadius` automatically applies `overflow: hidden` to the outer background ``, clipping background colors and images within rounded corners. - **Per-side borders set all other sides to `"none"`** explicitly to prevent Outlook Classic from rendering phantom black borders. Do not rely on default/inherited border values when using per-side borders. - **`height` is propagated to all nested tables** via both the `height` CSS property and the HTML `height` attribute for Outlook Classic compatibility. - **`mobile.wrap` requires 2+ children** to have any effect. A single-child row is never stacked. --- SECTION: IMAGE --- **Image Component — LLM Reference** `Image` renders a responsive, email-client-compatible image with optional linking. It uses a table wrapper for maximum compatibility across email clients (including Outlook) and supports desktop/mobile overrides, data bindings, and Outlook-specific pixel constraints. --- ## Position in the Document Tree ``` DocumentTree └── Section └── Container └── Column └── Text └── Image ← can be linked or standalone └── Spacer └── Button ``` `Image` is a leaf node. It can be placed anywhere a child component is accepted (inside `Column`, `Row`, `Container`, etc.). --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "ImageComponent", "config": { "src": "https://example.com/image.jpg", "alt": "Alt text description", "width": "100%", "maxWidth": "600px" } } ``` --- ## `ImageConfig` — Full Property Reference | Property | Type | Required | Default | Description | |-----------------------|-------------------------------|----------|-------------|-----------| | `src` | `string` | Yes | — | Image source URL | | `alt` | `string` | Yes | — | Alt text (required for accessibility) | | `width` | `string` | No | "100%" | Desktop width (e.g. "100%", "300px") | | `height` | `string` | No | "auto" | Desktop height | | `maxWidth` | `string` | No | "100%" | Maximum width | | `maxHeight` | `string` | No | — | Maximum height | | `backgroundColor` | `string` | No | — | Background color of wrapper cell | | `padding` | `string` | No | — | Padding around image (CSS string) | | `borderRadius` | `string` | No | "0" | Border radius | | `border` | `BorderConfig` | No | — | Border configuration | | `innerLink` | `IInnerLink` | No | — | Preferred linking object | | `href` | `string` | No | — | Deprecated. Use `innerLink` instead | | `objectFit` | `"contain" \| "cover" \| ...` | No | — | CSS `object-fit` | | `objectPosition` | `string` | No | — | CSS `object-position` | | `outlookWidth` | `string` | No | — | Pixel width for Outlook Classic (no "px") | | `mobile` | `ImageMobileConfig` | No | — | Mobile-specific overrides | --- ## `innerLink` Structure ```json "innerLink": { "type": "url" | "email" | "phone" | "anchor" | "page_top" | "page_bottom" | "none", "url"?: string, "email"?: string, "phone"?: string, "anchor"?: string, "target"?: "_blank" | "_self" } ``` --- ## Mobile Overrides (`mobile`) Only explicitly set properties are overridden on mobile. Unspecified properties inherit desktop values. ```json "mobile": { "width": "100%", "maxWidth": "100%", "borderRadius": "0px", "hidden": false } ``` --- ## How It Works The component renders inside a `` for email compatibility. It includes: - Desktop styles + mobile media query (`@media (max-width: 768px)`) - Outlook Classic support via MSO conditional comments when `outlookWidth` is set - Automatic link wrapping when `innerLink` is provided - `display: block` on the `` to remove unwanted gaps - Unique class names for safe mobile CSS targeting --- ## React Usage ```tsx import Image, { ImageConfig } from './Image'; const config: ImageConfig = { src: "https://example.com/image.jpg", alt: "Product showcase", width: "100%", maxWidth: "600px" }; ``` The component is memoized via `arePropsEqual`. --- ## Common Patterns & Examples ### 1. Basic responsive image ```json { "id": "img_hero", "type": "ImageComponent", "config": { "src": "https://cdn.example.com/hero.jpg", "alt": "Hero banner", "width": "100%", "maxWidth": "600px" } } ``` ### 2. Linked image ```json { "id": "cta_image", "type": "ImageComponent", "config": { "src": "https://example.com/button.jpg", "alt": "Shop Now", "innerLink": { "type": "url", "url": "https://example.com/shop", "target": "_blank" }, "outlookWidth": "600" } } ``` ### 3. Mobile-optimized image ```json { "id": "product_img", "type": "ImageComponent", "config": { "src": "https://example.com/product.jpg", "alt": "Product", "width": "280px", "mobile": { "width": "100%", "maxWidth": "100%" } } } ``` ### 4. Data-bound image (inside repeater) ```json { "id": "dynamic_img", "type": "ImageComponent", "config": { "src": "", "alt": "" }, "bindings": { "propertyMap": { "config.src": "item.image_url", "config.alt": "item.name" } } } ``` --- ## Data Bindings `Image` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Leaf components do not support `dataList` or `itemAlias` — use those on a parent `Container`, `Column`, or `Row` to repeat the image. `visible` and `propertyMap` are fully supported. ```json { "id": "dynamic-img", "type": "ImageComponent", "bindings": { "visible": "item.hasImage === true", "propertyMap": { "config.src": "item.imageUrl", "config.alt": "item.name" } }, "config": { "src": "", "alt": "", "width": "100%", "maxWidth": "600px" } } ``` | Property | Description | |---|---| | `visible` | JS expression evaluated against the Data Layer. Hides this component when falsy. | | `propertyMap` | Maps component property paths (prefixed with `config.`) to Data Layer paths. | > Bindable properties include `config.src`, `config.alt`, `config.width`, `config.maxWidth`, `config.backgroundColor`, and any other scalar `ImageConfig` field. `innerLink` itself cannot be bound via `propertyMap` — repeat the image inside a `Container` with `dataList` to vary links per item. --- ## Rules & Constraints - `src` and `alt` are **required**. - Prefer `innerLink` over the deprecated `href`/`target`. - Use `outlookWidth` (numeric string, e.g. `"600"`) when you need reliable fixed-width rendering in Outlook Classic. - `mobile` properties are **overrides only** — they do not reset desktop values unless explicitly set. - Always provide meaningful `alt` text. - For completely hidden images on mobile, use `mobile: { "hidden": true }`. - Non-pixel values in `width`/`height` are handled gracefully but `px` or `%` are recommended for predictability. --- SECTION: ICON --- **Icon Component — LLM Reference** `Icon` renders a dynamically generated icon using the Iconify API. It supports color, size, rotation, background, borders, and clickable links while maintaining excellent email client compatibility (including Outlook via VML). --- ## Position in the Document Tree ``` DocumentTree └── Section └── Container └── Column └── Text └── Icon ← inline or standalone icon └── Spacer └── Button ``` `Icon` is a leaf node. It can be placed anywhere a child component is accepted. --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "IconComponent", "config": { "iconIdentifier": "mdi:arrow-right", "width": "24", "color": "#000000" } } ``` --- ## `IconConfig` — Full Property Reference | Property | Type | Required | Default | Description | |-----------------------|-------------------------------------------|----------|-------------|-----------| | `iconIdentifier` | `string` | Yes | — | Iconify icon name (e.g. `"mdi:home"`, `"logos:react"`) | | `width` | `string \| number` | No | 24 | Icon width (px) | | `height` | `string \| number` | No | 24 | Icon height (px) | | `rotate` | `number` | No | 0 | Rotation angle in degrees | | `rotateOrientation` | `"cw" \| "ccw"` | No | `"cw"` | Rotation direction | | `color` | `string` | No | `"#000000"` | Icon color (hex recommended) | | `innerLink` | `IInnerLink` | No | — | Makes icon clickable | | `backgroundColor` | `string` | No | — | Background color of container | | `padding` | `string` | No | `"0"` | Padding around icon | | `borderRadius` | `string` | No | `"0"` | Border radius of container | | `border` | `BorderConfig` | No | — | Border configuration | | `justifyContent` | `"start" \| "center" \| "end"` | No | `"center"` | Horizontal alignment | --- ## `innerLink` Structure ```json "innerLink": { "type": "url" | "email" | "phone" | "anchor" | "page_top" | "page_bottom" | "none", "url"?: string, "email"?: string, "phone"?: string, "anchor"?: string, "target"?: "_blank" | "_self" } ``` --- ## How It Works - Icons are generated at runtime via the Iconify API as PNG images. - Uses a nested table structure for email compatibility. - When `backgroundColor` + `borderRadius` are used, VML (``) is injected for Outlook Classic support. - `display: block`, `fontSize: 0`, and `lineHeight: 0` are used to eliminate unwanted spacing. - Alignment is handled via the `align` attribute on the outer table. --- ## React Usage ```tsx import Icon, { IconConfig } from './Icon'; const config: IconConfig = { iconIdentifier: "mdi:check-circle", width: 32, color: "#22c55e", justifyContent: "center" }; ``` The component is memoized via `arePropsEqual`. --- ## Common Patterns & Examples ### 1. Simple icon ```json { "id": "icon_check", "type": "IconComponent", "config": { "iconIdentifier": "mdi:check", "width": 24, "color": "#10b981" } } ``` ### 2. Clickable icon (linked) ```json { "id": "icon_link", "type": "IconComponent", "config": { "iconIdentifier": "mdi:arrow-right", "width": 20, "color": "#3b82f6", "innerLink": { "type": "url", "url": "https://example.com", "target": "_blank" } } } ``` ### 3. Icon with background and border radius ```json { "id": "icon_bg", "type": "IconComponent", "config": { "iconIdentifier": "mdi:star", "width": 28, "color": "#ffffff", "backgroundColor": "#eab308", "padding": "8px", "borderRadius": "9999px" } } ``` ### 4. Rotated icon ```json { "id": "icon_rotate", "type": "IconComponent", "config": { "iconIdentifier": "mdi:refresh", "width": 24, "rotate": 45, "color": "#64748b" } } ``` ### 5. Right-aligned icon ```json { "id": "icon_align", "type": "IconComponent", "config": { "iconIdentifier": "mdi:chevron-right", "width": 18, "color": "#94a3b8", "justifyContent": "end" } } ``` --- ## Data Bindings `Icon` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Leaf components do not support `dataList` or `itemAlias` — use those on a parent `Container`, `Column`, or `Row` to repeat the component. `visible` and `propertyMap` are fully supported. ```json { "id": "dynamic-icon", "type": "IconComponent", "bindings": { "visible": "item.hasIcon === true", "propertyMap": { "config.iconIdentifier": "item.iconName", "config.color": "item.iconColor" } }, "config": { "iconIdentifier": "mdi:check-circle", "width": 24, "color": "#10b981" } } ``` | Property | Description | |---|---| | `visible` | JS expression evaluated against the Data Layer. Hides this component when falsy. | | `propertyMap` | Maps component property paths (prefixed with `config.`) to Data Layer paths. | > Bindable properties include `config.iconIdentifier`, `config.color`, `config.backgroundColor`, `config.width`, `config.height`, and any other scalar `IconConfig` field. --- ## Rules & Constraints - `iconIdentifier` is **required** — without it the component renders nothing. - Use full Iconify format: `"{collection}:{icon-name}"` (e.g. `mdi:home`, `logos:figma`, `ion:heart`). - Width and height should be numbers or pixel strings (e.g. `24` or `"24px"`). - Hex colors (`#rrggbb`) are recommended for best compatibility. - `borderRadius` + `backgroundColor` together trigger VML for Outlook rounded backgrounds. - The icon is always rendered with `object-fit: contain`. - `alt` text is intentionally empty (`""`) as icons are decorative in most email use cases. --- SECTION: HTML --- **Html Component — LLM Reference** The `Html` component is the absolute root of the email document. It wraps the entire tree, provides the necessary XML namespaces for legacy Outlook (VML) support, sets the document language, and defines the global background color that fills the email client's viewport. --- ## Position in the Document Tree `Html` is the **root component**. It is the first tag in the generated output and contains exactly two primary children: the `Head` and the `Body`. ```tsx {/* Content */} ``` --- ## ComponentNode JSON Structure ```json { "id": "root_html", "type": "HtmlComponent", "config": { "backgroundColor": "#ffffff", "lang": "en" }, "children": [ { "id": "head_1", "type": "HeadComponent", "config": { ... } }, { "id": "body_1", "type": "BodyComponent", "config": { ... } } ] } ``` --- ## `HtmlConfig` — Full Property Reference | Property | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `backgroundColor` | `string` | No | `"#ffffff"` | The background color of the entire email client canvas (viewport). | | `lang` | `string` | No | `"en"` | Sets the `lang` and `xml:lang` attributes for accessibility. | | `children` | `ComponentNode[]` | Yes | — | Must contain a `Head` and a `Body` component. | --- ## How It Works * **Namespace Injection:** Automatically adds `xmlns:v` and `xmlns:o` to the `` tag. This is critical for rendering vector shapes (VML) and specific office-based fixes in Outlook 2003-2019. * **Legacy Backgrounds:** Uses the `bgcolor` attribute on the `` tag. While modern web dev uses CSS, many email clients (especially older Outlook versions) rely on the `bgcolor` attribute on the root to render the canvas color correctly. * **React Implementation:** Uses `React.createElement` internally to bypass JSX validation rules that occasionally flag colon-namespaced attributes (like `xmlns:v`). --- ## React Usage ```tsx import Html from './Html'; import Head from './Head'; import Body from './Body'; {/* Email Rows */} ``` --- ## Common Patterns & Examples ### 1. Minimal Root Structure The standard entry point for every email generated by the builder. ```json { "id": "email_root", "type": "HtmlComponent", "config": { "backgroundColor": "#ffffff" }, "children": [ { "type": "HeadComponent", "id": "h", "config": {} }, { "type": "BodyComponent", "id": "b", "config": {} } ] } ``` ### 2. Dark Mode Preview Background Setting a dark global background for high-contrast or themed templates. ```json { "id": "email_root", "type": "HtmlComponent", "config": { "backgroundColor": "#1a1a1a", "lang": "en" }, "children": [...] } ``` --- ## Rules & Constraints * **Strict Hierarchy:** An `Html` node must be the top-most node in the JSON `DocumentTree`. * **Required Children:** It should always contain exactly one `Head` and one `Body`. * **Accessibility:** Always ensure the `lang` property matches the primary language of the email content for screen reader compatibility. * **Background Sync:** For the best visual experience, the `backgroundColor` of the `Html` component should either match the `Body` background or be a complementary "outer" color (e.g., a light gray `Html` background with a white `Body` container). --- SECTION: HEADING --- **Heading Component — LLM Reference** `Heading` renders a semantic HTML heading (`

` to `

`) with full text styling support. It is optimized for email clients with a table wrapper for reliable padding, background, and alignment. --- ## Position in the Document Tree ``` DocumentTree └── Section └── Container └── Column └── Heading ← semantic heading (h1–h6) └── Spacer └── Text ``` `Heading` is a leaf node. It can be placed anywhere a child component is accepted. --- ## ComponentNode JSON Structure ```json { "id": "unique-string-id", "type": "HeadingComponent", "config": { "text": "

Welcome to our newsletter

", "level": "h1", "color": "#1f2937", "fontSize": "32px" } } ``` --- ## `HeadingConfig` — Full Property Reference | Property | Type | Required | Default | Description | |--------------------|-----------------------------------|----------|-----------|-----------| | `text` | `string` (HTML) | No | — | Heading content. Can include inline HTML. | | `level` | `"h1" \| "h2" \| "h3" \| "h4" \| "h5" \| "h6"` | No | `"h1"` | Semantic HTML heading level | | `padding` | `string` | No | — | Padding around the heading (e.g. `"20px 0"`) | | `color` | `string` | No | — | Text color (hex recommended) | | `textAlign` | `"left" \| "center" \| "right" \| "justify"` | No | — | Horizontal alignment | | `fontFamily` | `string` | No | — | Font family stack | | `fontSize` | `string` | No | — | Font size (e.g. `"28px"`) | | `fontWeight` | `string` | No | — | Font weight (`"400"`, `"700"`, `"bold"`, etc.) | | `fontStyle` | `string` | No | — | `"italic"` or `"normal"` | | `lineHeight` | `string` | No | — | Line height (e.g. `"1.3"` or `"34px"`) | | `letterSpacing` | `string` | No | — | Letter spacing (e.g. `"0.5px"`) | | `textTransform` | `string` | No | — | `"uppercase"`, `"capitalize"`, etc. | | `textDecoration` | `string` | No | — | `"underline"`, `"line-through"` | | `backgroundColor` | `string` | No | — | Background color of the block | | `verticalAlign` | `string` | No | `"top"` | Vertical alignment in the cell | | `wordBreak` | `string` | No | — | Word break behavior | | `whiteSpace` | `string` | No | — | White space handling | --- ## How It Works - Renders inside a `
` → `
` wrapper for consistent padding, background, and width control across email clients. - Default browser margins on heading tags are reset (`margin: 0`). - Supports rich HTML content in `text` with automatic link style injection. - Uses inline styles for maximum email client compatibility. - `msoLineHeightRule: exactly` is applied for better Outlook rendering. --- ## React Usage ```tsx import Heading, { HeadingConfig } from './Heading'; const config: HeadingConfig = { text: "Our Latest Updates", level: "h2", fontSize: "28px", color: "#111827", textAlign: "center" }; ``` The component is memoized via `arePropsEqual`. --- ## Common Patterns & Examples ### 1. Main hero heading ```json { "id": "hero_heading", "type": "HeadingComponent", "config": { "text": "Summer Sale is Here!", "level": "h1", "fontSize": "36px", "color": "#1f2937", "textAlign": "center", "padding": "20px 0 12px 0" } } ``` ### 2. Section heading ```json { "id": "section_title", "type": "HeadingComponent", "config": { "text": "Featured Products", "level": "h2", "fontSize": "24px", "fontWeight": "700", "color": "#374151", "padding": "32px 0 16px 0" } } ``` ### 3. Centered heading with background ```json { "id": "bg_heading", "type": "HeadingComponent", "config": { "text": "Special Offer", "level": "h3", "fontSize": "22px", "color": "#ffffff", "backgroundColor": "#3b82f6", "textAlign": "center", "padding": "16px 0", "fontWeight": "600" } } ``` ### 4. Heading with custom line height and letter spacing ```json { "id": "tight_heading", "type": "HeadingComponent", "config": { "text": "Limited Time Deal", "level": "h2", "fontSize": "30px", "lineHeight": "1.1", "letterSpacing": "-0.5px", "fontWeight": "800" } } ``` --- ## Data Bindings `Heading` supports the `bindings` property on the `ComponentNode`, alongside `id`, `type`, and `config`. Leaf components do not support `dataList` or `itemAlias` — use those on a parent `Container`, `Column`, or `Row` to repeat the component. `visible` and `propertyMap` are fully supported. ```json { "id": "dynamic-heading", "type": "HeadingComponent", "bindings": { "visible": "section.showTitle === true", "propertyMap": { "config.text": "section.title", "config.color": "section.titleColor" } }, "config": { "text": "Fallback Heading", "level": "h2", "fontSize": "28px", "color": "#1f2937" } } ``` | Property | Description | |---|---| | `visible` | JS expression evaluated against the Data Layer. Hides this component when falsy. | | `propertyMap` | Maps component property paths (prefixed with `config.`) to Data Layer paths. | > Bindable properties include `config.text`, `config.color`, `config.backgroundColor`, `config.fontSize`, `config.fontFamily`, `config.padding`, and any other scalar `HeadingConfig` field. --- ## Rules & Constraints - Use the `text` property to set content. It supports basic HTML (e.g. ``, ``, ``). - `level` should always be specified for semantic correctness. - Always define `fontSize` explicitly — do not rely on default browser sizes for headings in email. - `padding` and `backgroundColor` are applied to the wrapping table cell. - Default top/bottom margins are removed — use `padding` for spacing control. - For very complex HTML content, consider using the `Text` component instead. --- SECTION: HEAD --- **Head Component — LLM Reference** `Head` generates the `` section of the email document. It includes essential meta tags, global CSS resets, mobile responsiveness rules, Outlook fixes, and font declarations. --- ## Position in the Document Tree `Head` is a **top-level** component and must be placed directly inside the email root (not in the body). ```tsx {/* Fonts + custom styles */}
{/* Content */}
``` --- ## ComponentNode JSON Structure ```json { "id": "head", "type": "HeadComponent", "config": { "title": "Monthly Newsletter", "backgroundColor": "#ffffff", "rowGaps": ["8px", "16px", "32px", "48px"] } } ``` --- ## `HeadConfig` — Full Property Reference | Property | Type | Required | Default | Description | |--------------------|-----------------------|----------|----------------------|-----------| | `title` | `string` | No | `"Email Preview"` | Email subject / browser title | | `backgroundColor` | `string` | No | `"#ffffff"` | Global email background color | | `rowGaps` | `string[]` | No | `[]` | Gap values used for mobile responsive spacing | | `fonts` | `ResolvedFont[]` | No | `[]` | Auto-resolved fonts (from builder pipeline) | | `children` | `ReactNode` | No | — | Additional custom `