Table
The standard data table in REFRACT PDF. Renders a sorted or grouped list of records from the JSON payload with configurable columns, aggregates, borders, and stripe styling.
Overview
A Table (table) binds to an array in the JSON payload (via dataPath) and renders every record as a table row. Each column definition maps a data key to a header label with optional formatting, alignment, and a secondary sub-text line. Tables support grouping by a field, per-group and overall aggregates, overflow sub-rows with per-cell conditional styles, and a full set of border and stripe overrides.
Title & Icon
| Property | Type | Default | Description |
|---|---|---|---|
title | string | "Table" | Heading displayed above the table. Supports {FIELDNAME} tokens for live data values. |
titleIcon | select | none | Decorative icon next to the title. Accepts any key from the icon registry (e.g. package, list, calendar). |
accentColor | color | theme.primary | Color applied to the title text and icon. |
showTitle | toggle | true | When disabled, the title row is completely omitted - useful when the section heading already names the table. |
Data Binding
| Property | Type | Default | Description |
|---|---|---|---|
dataPath | string | "items" | Dot-path into the JSON payload that resolves to the array of records. Example: "lines", "order.lineItems". |
Columns
Each entry in columns describes one vertical slice of the table.
| Property | Type | Default | Description |
|---|---|---|---|
key | string | none | Data field name - must match a key on each row object. |
label | string | none | Column header text. |
width | number | "auto" | "*" | "*" | Column width in pt, "auto" (fit content), or "*" (fill remaining space). |
align | "left" | "right" | "center" | "left" | Text alignment for both the header and data cells. |
format | string | "text" | Formatting hint applied at render time. Supported values: text, number, currency, date, datetime, boolean, percentage. |
subText | string | none | Static secondary line rendered below the main cell value in a smaller gray font (8pt). Useful for units or descriptions. |
Mixing Column Widths
Column widths follow the same rules as the underlying document engine. Mix fixed pt values with "*" fill columns - for example [80, "*", 60] - to achieve proportional layouts.
Column Manifest (Advanced)
When a columnManifest is provided, the table switches to advanced column mode, which unlocks overflow sub-rows and per-cell conditional styles. The plain columns array is ignored when a columnManifest is present.
Advanced column mode is also engaged automatically when:
- Any column has
primary: false(overflow column). - Any column defines
conditionalStyles. borderStyleis set to"minimal".
Border & Line Styles
| Property | Type | Default | Description |
|---|---|---|---|
showBorders | boolean | true | Master switch. When false, all lines are suppressed. |
borderStyle | "full" | "minimal" | "full" | "full" - full grid with outer frame, vertical dividers, and row separators. "minimal" - row separators only; outer frame and vertical lines are hidden. Automatically engages the manifest renderer. |
borderColor | color | theme.borderColor | Color applied to all table lines. |
vLineWidth | number | 0.5 | Width (pt) of all vertical divider lines. Only applied when borderStyle is "full". |
hLineWidth | number | 0.5 | Width (pt) of horizontal row-separator lines. |
headerLineWidth | number | 1.5 | Width (pt) of the thick line that separates the header row from the first data row. |
Header & Stripe Styling
| Property | Type | Default | Description |
|---|---|---|---|
headerBackground | color | theme.primary | Fill color of the header row. |
headerText | color | theme.textOnPrimary | Text color for header labels. |
stripeShade | color | theme.stripeShade | Fill color applied to every other data row (zebra stripes). |
stripeOpacity | number (0-1) | 1 | Opacity of the zebra-stripe fill. Set to 0 to disable stripes entirely. |
Theme Tokens vs Hex Values
The color properties above support theme token strings (e.g. "theme.primary", "theme.borderColor") as well as explicit hex values. Omitting a property entirely falls back to the active theme automatically - only set these when you want to override the theme for a specific table.
Grouping
Grouping splits table rows into visually separated bands keyed by a field value.
| Property | Path | Description |
|---|---|---|
grouping.by | props.grouping.by | Field name used as the group key. Rows with the same value are placed in the same band. |
grouping.header.show | props.grouping.header.show | Show a full-width header row at the top of each group band. Default: true. |
grouping.header.template | props.grouping.header.template | Template string for the group header cell. Supports {{fieldName}} tokens. Default: the raw group-key value. |
grouping.footer.show | props.grouping.footer.show | Show a full-width footer row at the bottom of each group band. Default: false. |
grouping.footer.template | props.grouping.footer.template | Template string for the group footer cell. |
grouping.pageBreakPerGroup | props.grouping.pageBreakPerGroup | Insert a page break before every group band except the first. |
grouping.keepGroupTogether | props.grouping.keepGroupTogether | Keep each group band on a single page, preventing splits across page boundaries. |
Group Key Column
The column whose key matches grouping.by is automatically removed from the table columns so it does not appear as a redundant data column.
Aggregates
Aggregate rows appear as summary lines below the table data (or per group band). Each aggregate rule defines an operation, a label, and the target column where the result is rendered.
| Property | Description |
|---|---|
op | Aggregate function: sum, avg, count, min, max. |
label | Text displayed in the first cell of the aggregate row (e.g. "Total", "Subtotal"). |
targetColumnId / targetColumn / key | Identifies the column that receives the computed value. Checked against column id first, then key. |
Configure table-level aggregates via props.aggregates[]. Configure per-group aggregates via props.grouping.aggregates[].
Pagination
| Property | Path | Default | Description |
|---|---|---|---|
breakBefore | pagination.breakBefore | false | Insert a page break immediately before the table block. |
breakAfter | pagination.breakAfter | false | Insert a page break immediately after the table block. |
keepTogether | pagination.keepTogether | false | Prevent the document engine from splitting the table across pages. |
tableBreakAfterNRows | pagination.tableBreakAfterNRows | none | Force a page break after every N data rows. Useful for very tall tables that must start fresh every page. |
Conditional Visibility
| Property | Path | Description |
|---|---|---|
visibleWhen | expressions.visibleWhen | JavaScript expression evaluated against the JSON payload. When the expression returns falsy, the entire table block is omitted from the document. |
valueWhen | expressions.valueWhen | Expression that replaces the rendered value at runtime (advanced use). |
See Conditional Visibility & Layout Collapse for expression syntax and the distinction from props.visible.
Examples
Theme-driven table (recommended)
Omit color properties to inherit the active theme. Use "theme.*" tokens to reference theme values explicitly without hardcoding hex.
{
"type": "table",
"props": {
"title": "Order Lines",
"titleIcon": "list",
"dataPath": "lines",
"showTitle": true,
"columns": [
{ "key": "partNumber", "label": "Part #", "width": 80, "align": "left" },
{ "key": "description", "label": "Description", "width": "*", "align": "left" },
{ "key": "qty", "label": "Qty", "width": 50, "align": "right", "format": "number" },
{ "key": "unitPrice", "label": "Unit Price", "width": 70, "align": "right", "format": "currency" },
{ "key": "lineTotal", "label": "Total", "width": 70, "align": "right", "format": "currency" }
],
"aggregates": [
{ "op": "sum", "label": "Grand Total", "key": "lineTotal" }
],
"stripeOpacity": 0.5,
"borderStyle": "full"
}
}With explicit color overrides
Use "theme.*" tokens to pin to theme values, or provide a hex string to fully override.
{
"type": "table",
"props": {
"title": "Order Lines",
"titleIcon": "list",
"dataPath": "lines",
"columns": [
{ "key": "partNumber", "label": "Part #", "width": 80, "align": "left" },
{ "key": "description", "label": "Description", "width": "*", "align": "left" },
{ "key": "qty", "label": "Qty", "width": 50, "align": "right", "format": "number" },
{ "key": "unitPrice", "label": "Unit Price", "width": 70, "align": "right", "format": "currency" },
{ "key": "lineTotal", "label": "Total", "width": 70, "align": "right", "format": "currency" }
],
"aggregates": [
{ "op": "sum", "label": "Grand Total", "key": "lineTotal" }
],
"headerBackground": "theme.primary",
"headerText": "theme.textOnPrimary",
"borderColor": "theme.borderColor",
"stripeShade": "theme.stripeShade",
"stripeOpacity": 0.5,
"borderStyle": "full"
}
}Grouped table with aggregates
{
"type": "table",
"props": {
"title": "Lines by Warehouse",
"dataPath": "lines",
"columns": [
{ "key": "partNumber", "label": "Part #", "width": 80, "align": "left" },
{ "key": "description", "label": "Description", "width": "*", "align": "left" },
{ "key": "qty", "label": "Qty", "width": 50, "align": "right", "format": "number" },
{ "key": "warehouse", "label": "Warehouse", "width": 80, "align": "left" }
],
"grouping": {
"by": "warehouse",
"header": { "show": true, "template": "Warehouse: {{warehouse}}" },
"footer": { "show": false },
"pageBreakPerGroup": false,
"keepGroupTogether": true,
"aggregates": [
{ "op": "sum", "label": "Subtotal Qty", "key": "qty" }
]
},
"aggregates": [
{ "op": "sum", "label": "Grand Total Qty", "key": "qty" }
],
"borderStyle": "full"
}
}Column-aligned aggregate totals
Target a visible primary column to place the aggregate value under that column. Each aggregate rule renders as its own footer row, which is useful when the totals should stay aligned with the table headers.
{
"type": "table",
"props": {
"title": "Itemised Time Entries",
"dataPath": "lines",
"showTitle": false,
"columnManifest": {
"maxCols": 5,
"columns": [
{ "key": "description", "label": "DESCRIPTION", "displayOrder": 1, "primary": false, "align": "left", "format": "text", "width": "*" },
{ "key": "date", "label": "DATE", "displayOrder": 2, "primary": true, "align": "center", "format": "date", "width": "*" },
{ "key": "hours", "label": "HOURS", "displayOrder": 3, "primary": true, "align": "center", "format": "number", "width": 62 },
{ "key": "rate", "label": "RATE (USD)", "displayOrder": 4, "primary": true, "align": "right", "format": "currency", "width": 82 },
{ "key": "amount", "label": "AMOUNT (USD)", "displayOrder": 5, "primary": true, "align": "right", "format": "currency", "width": 88 }
],
"overflowStrategy": {
"mode": "additionalRow",
"columnsPerRow": 1,
"indent": 8,
"columnGap": 8,
"borderStyle": "dashed",
"fontSizeAdjustment": -1
}
},
"aggregates": [
{
"op": "sum",
"label": "Total Hours",
"field": "hours",
"targetColumnId": "hours"
},
{
"op": "sum",
"label": "Total Amount",
"field": "amount",
"targetColumnId": "amount"
}
]
}
}Aggregate Values Must Be Numeric
Aggregate operations only include numeric payload values. Store values like "hours": 3.75 and "amount": 750, then let the column format render the display value.
Combined full-span aggregate summary
In advanced column mode, target an overflow column (primary: false) or an unmapped key to combine multiple aggregate rules into one full-width summary row. This is useful for compact totals such as Total Hours: 4.75 | Total Amount: $950.00.
{
"type": "table",
"props": {
"title": "Itemised Time Entries",
"dataPath": "lines",
"showTitle": false,
"columnManifest": {
"maxCols": 5,
"columns": [
{ "key": "description", "label": "DESCRIPTION", "displayOrder": 1, "primary": false, "align": "left", "format": "text", "width": "*" },
{ "key": "date", "label": "DATE", "displayOrder": 2, "primary": true, "align": "center", "format": "date", "width": "*" },
{ "key": "hours", "label": "HOURS", "displayOrder": 3, "primary": true, "align": "center", "format": "number", "width": 62 },
{ "key": "rate", "label": "RATE (USD)", "displayOrder": 4, "primary": true, "align": "right", "format": "currency", "width": 82 },
{ "key": "amount", "label": "AMOUNT (USD)", "displayOrder": 5, "primary": true, "align": "right", "format": "currency", "width": 88 }
],
"overflowStrategy": {
"mode": "additionalRow",
"columnsPerRow": 1,
"indent": 8,
"columnGap": 8,
"borderStyle": "dashed",
"fontSizeAdjustment": -1
}
},
"aggregates": [
{
"op": "sum",
"label": "Total Hours",
"field": "hours",
"targetColumnId": "description"
},
{
"op": "sum",
"label": "Total Amount",
"field": "amount",
"targetColumnId": "description"
}
]
}
}Minimal border style with conditional status column
{
"type": "table",
"props": {
"title": "Shipments",
"dataPath": "shipments",
"borderStyle": "minimal",
"columnManifest": {
"maxCols": 4,
"columns": [
{ "key": "trackingNumber", "label": "Tracking #", "displayOrder": 1, "primary": true, "width": 100, "align": "left" },
{ "key": "carrier", "label": "Carrier", "displayOrder": 2, "primary": true, "width": 80, "align": "left" },
{ "key": "eta", "label": "ETA", "displayOrder": 3, "primary": true, "width": 80, "align": "left", "format": "date" },
{ "key": "status", "label": "Status", "displayOrder": 4, "primary": true, "width": 80, "align": "center",
"conditionalStyles": [
{ "value": "Delivered", "icon": "CheckCircle", "color": "#10B981", "bgColor": "#ECFDF5" },
{ "value": "In Transit", "icon": "AlertTriangle", "color": "#B45309", "bgColor": "#FEF3C7" },
{ "value": "Exception", "icon": "XCircle", "color": "#EF4444", "bgColor": "#FEF2F2" },
{ "value": "default", "icon": "", "color": "#374151", "bgColor": "#F9FAFB" }
]
}
],
"overflowStrategy": {
"mode": "additionalRow",
"columnsPerRow": 2,
"indent": 8,
"columnGap": 6,
"borderStyle": "dashed",
"fontSizeAdjustment": -1
}
}
}
}Overflow columns: grid layout
Columns with primary: false render as key-value pairs in a grid sub-row beneath each data row. columnsPerRow controls how many pairs appear side-by-side.
{
"type": "table",
"props": {
"title": "Purchase Orders",
"dataPath": "orders",
"columnManifest": {
"maxCols": 5,
"columns": [
{ "key": "poNumber", "label": "PO #", "displayOrder": 1, "primary": true, "width": 80, "align": "left" },
{ "key": "vendor", "label": "Vendor", "displayOrder": 2, "primary": true, "width": "*", "align": "left" },
{ "key": "orderDate", "label": "Order Date", "displayOrder": 3, "primary": true, "width": 80, "align": "left", "format": "date" },
{ "key": "total", "label": "Total", "displayOrder": 4, "primary": true, "width": 80, "align": "right", "format": "currency" },
{ "key": "shipVia", "label": "Ship Via", "displayOrder": 5, "primary": false, "align": "left" },
{ "key": "trackingNo", "label": "Tracking #", "displayOrder": 6, "primary": false, "align": "left" },
{ "key": "warehouse", "label": "Warehouse", "displayOrder": 7, "primary": false, "align": "left" },
{ "key": "payTerms", "label": "Pay Terms", "displayOrder": 8, "primary": false, "align": "left" }
],
"overflowStrategy": {
"mode": "additionalRow",
"columnsPerRow": 2,
"indent": 12,
"columnGap": 8,
"borderStyle": "dashed",
"fontSizeAdjustment": -1
}
}
}
}Overflow columns: full-span rows
Use span: "full" for long-text fields that should occupy the entire row width. Full-span rows always get their own line and can carry an icon and custom fill.
{
"type": "table",
"props": {
"title": "Contracts",
"dataPath": "contracts",
"columnManifest": {
"maxCols": 4,
"columns": [
{ "key": "contractId", "label": "Contract #", "displayOrder": 1, "primary": true, "width": 90, "align": "left" },
{ "key": "counterparty", "label": "Counterparty", "displayOrder": 2, "primary": true, "width": "*", "align": "left" },
{ "key": "startDate", "label": "Start", "displayOrder": 3, "primary": true, "width": 80, "align": "left", "format": "date" },
{ "key": "endDate", "label": "End", "displayOrder": 4, "primary": true, "width": 80, "align": "left", "format": "date" },
{
"key": "summary", "label": "Summary", "displayOrder": 5, "primary": false,
"layout": { "span": "full", "icon": "FileText", "showTitle": true }
},
{
"key": "legalNotes", "label": "Legal Notes", "displayOrder": 6, "primary": false,
"layout": {
"span": "full", "icon": "Shield", "showTitle": true,
"style": { "bgColor": "#FEF3C7", "textColor": "#92400E", "fontWeight": "normal" }
}
}
],
"overflowStrategy": {
"mode": "additionalRow",
"columnsPerRow": 2,
"indent": 0,
"columnGap": 8,
"borderStyle": "solid",
"fontSizeAdjustment": 0
}
}
}
}Mixed overflow: grid pairs and full-span rows
Grid pairs (span: "auto") and full-span rows (span: "full") can coexist in the same manifest. Ordering is by displayOrder - grid pairs accumulate until a full-span column is encountered, which flushes the partial grid row first.
"columns": [
{ "key": "id", "label": "ID", "displayOrder": 1, "primary": true, "width": 60 },
{ "key": "name", "label": "Name", "displayOrder": 2, "primary": true, "width": "*" },
{ "key": "amount", "label": "Amount", "displayOrder": 3, "primary": true, "width": 80, "format": "currency" },
{ "key": "category", "label": "Category", "displayOrder": 4, "primary": false },
{ "key": "region", "label": "Region", "displayOrder": 5, "primary": false },
{
"key": "notes", "label": "Notes", "displayOrder": 6, "primary": false,
"layout": { "span": "full", "icon": "FileText" }
},
{
"key": "tags", "label": "Tags", "displayOrder": 7, "primary": false,
"layout": { "span": "full", "displayAsList": true, "showTitle": false }
}
]showTitle: false on Full-span Columns
showTitle: false on a full-span column omits the label cell entirely - the value spans the full row width. Useful for plain narrative text that needs no heading.
Overflow with separator between grid pairs
"overflowStrategy": {
"mode": "additionalRow",
"columnsPerRow": 3,
"indent": 12,
"columnGap": 10,
"borderStyle": "dashed",
"fontSizeAdjustment": -1,
"separator": {
"enabled": true,
"type": "|",
"width": 8,
"color": "#CBD5E1"
}
}Spacer / Separator
A lightweight visual divider and vertical spacing control. Renders as a horizontal rule (solid or dashed) with configurable thickness, color, and surrounding whitespace, or as an invisible spacer block when the line is hidden.
Hierarchical Table
A multi-level adaptive table in REFRACT PDF. Renders deeply nested JSON arrays as progressive depth tables with per-level styling, overflow sub-rows, conditional cell styles, computations, and hierarchyRules-driven layout.