> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tipar.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Tables

> Build a grid with sized columns, an optional header, and a body that repeats one row per item in your data.

A `table` is a grid of `columns` with an optional `header` row and a `body` that **repeats one row per element** in an array from your data. Repeating table rows is the only loop in the template language — there are no other loops or conditionals.

```json theme={"dark"}
{
  "type": "table",
  "columns": [
    { "kind": "relative", "weight": 5 },
    { "kind": "relative", "weight": 1 },
    { "kind": "relative", "weight": 2 }
  ],
  "header": {
    "row": [
      { "child": { "type": "text", "value": "Item", "style": { "semiBold": true } }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 },
      { "child": { "type": "text", "value": "Qty", "style": { "semiBold": true }, "align": "right" }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 },
      { "child": { "type": "text", "value": "Price", "style": { "semiBold": true }, "align": "right" }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 }
    ]
  },
  "body": {
    "forEach": "receipt.items",
    "row": [
      { "child": { "type": "text", "value": "{{item.name}}" }, "padding": 2 },
      { "child": { "type": "text", "value": "{{item.qty}}", "align": "right" }, "padding": 2 },
      { "child": { "type": "text", "value": "{{item.price}}", "align": "right" }, "padding": 2 }
    ]
  }
}
```

| Field     | Type               | Required | Notes                                                                                    |
| --------- | ------------------ | -------- | ---------------------------------------------------------------------------------------- |
| `columns` | Column\[]          | **yes**  | At least one. Defines the grid's width structure.                                        |
| `header`  | `{ row: Cell[] }`  | no       | A header row drawn once at the top (and repeated atop each page for a multi-page table). |
| `body`    | `{ forEach, row }` | **yes**  | The repeating part — see below.                                                          |

## Columns

Columns are sized exactly like [`row` cells](/templates/layout#sizing-a-cell) — `relative` by weight, or `constant` by width:

```json theme={"dark"}
"columns": [
  { "kind": "relative", "weight": 5 },
  { "kind": "constant", "width": 80 }
]
```

The number of columns sets the grid; every header and body row must supply exactly that many cells.

## Repeating rows with forEach

The body's `forEach` is a [dotted path](/templates/overview#interpolation) into `data` that must resolve to an **array**. Tipar renders the `row` template once per element, binding the current element to `item`:

```json theme={"dark"}
"body": {
  "forEach": "invoice.lines",
  "row": [
    { "child": { "type": "text", "value": "{{item.description}}" } },
    { "child": { "type": "text", "value": "{{item.amount}}", "align": "right" } }
  ]
}
```

Given this data, the body renders three rows:

```json theme={"dark"}
{
  "invoice": {
    "lines": [
      { "description": "Pro plan", "amount": "€49.00" },
      { "description": "Extra seats", "amount": "€30.00" },
      { "description": "SMS credits", "amount": "€8.00" }
    ]
  }
}
```

<Note>
  `item` is only defined **inside** a table body. Referencing `{{item.x}}` anywhere else, or pointing `forEach` at something that isn't an array, fails with `422`.
</Note>

<Tip>
  Need a row that *isn't* part of the loop — a totals line, a "no items" note? Put it **outside** the table, as a sibling node in the surrounding `column`. The table is strictly the repeating grid; static lines live next to it.
</Tip>

## Cells

Every cell — header or body — has the same shape:

```json theme={"dark"}
{
  "child": { "type": "text", "value": "{{item.description}}" },
  "border": { "bottom": 1, "color": "grey.lighten2" },
  "padding": 3
}
```

| Field     | Type                | Required | Notes                                                           |
| --------- | ------------------- | -------- | --------------------------------------------------------------- |
| `child`   | Node                | **yes**  | Any node — usually `text`, but a `column` or `image` works too. |
| `border`  | `{ bottom, color }` | no       | A bottom rule under the cell. See below.                        |
| `padding` | number (points)     | no       | Vertical padding inside the cell.                               |

### Cell borders

In v1 a cell border is a **bottom rule only** — enough for the horizontal lines that make up most tables. Top, left, and right borders aren't supported yet.

| Field           | Type                             | Notes                                               |
| --------------- | -------------------------------- | --------------------------------------------------- |
| `border.bottom` | number (points)                  | Rule thickness.                                     |
| `border.color`  | [Color](/templates/text#colours) | Defaults to black; only drawn when `bottom` is set. |

A common pattern: a darker, heavier rule under the header, and a light hairline under each body row.

```json theme={"dark"}
"header": { "row": [ { "child": { "...": "..." }, "border": { "bottom": 1, "color": "grey.darken1" } } ] },
"body":   { "forEach": "...", "row": [ { "child": { "...": "..." }, "border": { "bottom": 1, "color": "grey.lighten2" } } ] }
```

<Warning>
  **Cell counts must match the column count.** If `columns` has three entries, every `header.row` and `body.row` must have exactly three cells. A mismatch is a `422` structural error — and the response names the offending row so you can fix it.
</Warning>

## Multi-page tables

You don't manage pagination. When a table's rows overflow the page, Tipar continues it on the next page automatically, re-drawing the `header` row at the top. Combine that with a [page-number footer](/templates/headers-footers) for long reports — see the [report example](/examples/gallery#multi-page-report).

<Card title="Next: Images" icon="image" href="/templates/images">
  Embed logos and graphics as base64.
</Card>
