# 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": "ImageComponent", "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": "ContainerComponent", "bindings": { "dataList": "products", "itemAlias": "product", "visible": "product.inStock === true" }, "config": { "widthType": "fixed", "width": "600px", "childrenConstraints": { "widthDistributionType": "equals" }, "padding": "16px" }, "children": [ { "id": "product-img", "type": "ImageComponent", "bindings": { "propertyMap": { "config.src": "product.image.src", "config.alt": "product.image.alt" } }, "config": { "src": "", "alt": "", "width": "100%" } }, { "id": "db-product-name", "type": "TextComponent", "bindings": { "propertyMap": { "config.text": "product.name" } }, "config": { "text": "Product name", "fontSize": "16px", "fontWeight": "700" } }, { "id": "product-price", "type": "TextComponent", "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": "db-section-promo", "type": "SectionComponent", "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": "ContainerComponent", "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": "ColumnComponent", "bindings": { "dataList": "job_listings", "itemAlias": "job" }, "config": { "padding": "0", "gap": "16px" }, "children": [ { "id": "job-title", "type": "HeadingComponent", "bindings": { "propertyMap": { "config.text": "job.title" } }, "config": { "text": "Job Title", "level": "h3", "fontSize": "18px" } }, { "id": "job-location", "type": "TextComponent", "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.