# 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": "row-social-icons",
"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": "row-product-cards",
"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": "row-social-bindings",
"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. |