Tables¶
Markdown pipe tables are cute until they meet real reports. The minute you
need grouped headers, wrapped text, row spans, totals, or a table that behaves
properly in LaTeX, plain Markdown turns into duct tape. TeXSmith keeps the
simple syntax for simple tables and adds a declarative yaml table fence for
the serious stuff.
Use the right level of power:
- Plain Markdown table: quick two-dimensional data, no layout drama.
- Plain Markdown table +
yaml table-config: keep the pipe table, add LaTeX layout metadata. yaml table: describe the whole table as structured data when spans, grouped headers, footers, or validation matter.
Captions and Labels¶
Put the standard TeXSmith caption line directly after the table:
```yaml table
columns: [Fruit, Geneva, Zurich, Basel]
rows:
- [Apples, 120, 180, 90]
- [Pears, 45, ~, 110]
```
Table: Fruit stock by warehouse {#tbl:stock}
The {#tbl:stock} part becomes the LaTeX \label{tbl:stock}, and @tbl:stock
refers to it. The caption line works the same on a plain Markdown table. Its
canonical position is after the block — after the table-config fence when
there is one — and a Table: line before the table stays accepted for Pandoc
compatibility (see captions for the
attachment rule).
Full YAML Tables¶
A full YAML table is a fenced code block whose info string is exactly
yaml table:
```yaml table
columns: [Fruit, Geneva, Zurich, Basel]
rows:
- [Apples, 120, 180, 90]
- [Pears, 45, ~, 110]
- separator: {label: Seasonal shortage}
- [Apricots, 5, 0, 12]
footer:
- [Total, 170, 180, 212]
```
Top-level keys:
| Key | Purpose |
|---|---|
table |
Optional table-level settings: width, placement, long-table mode. |
columns |
Required column tree. Needs at least two leaf columns. |
rows |
Body rows and separators. |
footer |
Summary rows rendered after an extra rule. |
The first column is the row-label column. In positional rows, the first item becomes that label and the remaining items fill the data columns.
~ is YAML null. In normal cells it renders as empty. In span rectangles it
is also the explicit “this slot is absorbed” marker, so the validator knows
you meant it.
Columns¶
The short form is just a list of names:
columns: [Article, Editor, Status, Pages]
For metadata, use mappings:
columns:
- name: Requirement
width: 25%
align: l
- name: Description
align: justify
- name: Priority
width: 15%
align: center
Column attributes:
| Attribute | Values |
|---|---|
align |
l, c, r, j, or long forms: left, center, centre, right, justify, justified. |
width |
auto, X, a percentage such as 25%, or a raw LaTeX length such as 3cm. |
width-group |
Any identifier. Columns with the same group share the same width. |
Grouped columns are recursive:
columns:
- Product
- name: FY23
columns: [Q1, Q2, Q3, Q4]
width-group: quarter
- name: FY24
columns: [Q1, Q2, Q3, Q4]
width-group: quarter
Groups can carry align, width, and width-group; those attributes
propagate to their leaf columns unless a child overrides them. Nested groups
produce multiple header rows and \cmidrule strokes in LaTeX.
Rows¶
The most compact form is positional:
rows:
- [Apples, 120, 180, 90]
- [Pears, 45, ~, 110]
For grouped columns, a list cell fills the leaves of that group:
columns:
- Product
- name: FY24
columns: [Q1, Q2, Q3, Q4]
rows:
- [Apples, [130, 145, 160, 150]]
Named-row mode trades brevity for typo detection. The row maps data by top-level column name, and missing columns render empty:
columns:
- Product
- name: FY23
columns: [Q1, Q2, Q3, Q4]
- name: FY24
columns: [Q1, Q2, Q3, Q4]
rows:
- Apples: {FY23: [120, 135, 150, 140], FY24: [130, 145, 160, 150]}
- label: Cherries
cells: {FY23: [~, ~, 45, 0]}
If you misspell FY23, TeXSmith fails locally with an inline table error
instead of silently producing a broken PDF.
Separators and Footers¶
A separator inserts a horizontal rule. It can also label a body section:
rows:
- [Apples, 120, 180, 90]
- separator: {label: Seasonal shortage}
- [Apricots, 5, 0, 12]
footer:
- [Total, 125, 180, 102]
Equivalent separator forms:
- separator: true
- separator:
label: Seasonal shortage
- separator:
label: Grand totals
double-rule: true
Footer rows use the same row syntax as body rows.
Rich Cells and Spans¶
A scalar cell is enough most of the time. When you need spans or a local alignment override, promote the cell to a mapping:
{value: Maria, rows: 2}
{value: "$570k", cols: 4, align: c}
{value: "Merged 2x3", rows: 2, cols: 3, align: center}
Row spans require the absorbed cells below to be ~:
```yaml table
columns: [Article, Editor, Status, Pages]
rows:
- [Alpha, {value: Maria, rows: 2}, Draft, 12]
- [Beta, ~, Review, 18]
- [Gamma, John, Published, 24]
```
Table: Article assignments {#tbl:articles}
Column spans consume consecutive leaf columns:
```yaml table
columns:
- Metric
- name: 2024
columns: [Q1, Q2, Q3, Q4]
rows:
- [Revenue, [120, 130, 150, 170]]
- [Cost, [80, 85, 90, 95]]
- separator: true
- [Gross, {value: "$570k", cols: 4, align: c}]
```
Table: Annual totals across all four quarters
A cell can span a rectangle. Every absorbed slot must be acknowledged:
columns: [A, B, C, D]
rows:
- [r1, {value: "Merged 2x3", rows: 2, cols: 3, align: c}]
- [r2, ~, ~, ~]
- [r3, x, y, z]
Widths and LaTeX Environments¶
By default, table.width is auto, which selects a natural-width tabular
unless a column asks for flexible width. Setting a table width usually selects
tabularx:
table:
width: 90%
columns:
- name: Requirement
width: 25%
align: left
- name: Description
align: justify
- name: Priority
width: 15%
align: center
Width rules:
table.width: automeans natural table width.table.width: 100%means\linewidth; other percentages scale from it.width: Xmarks a column as the flexibletabularxcolumn.width: autoon a column is also treated as a flexibleXcolumn.width-group: quartermakes all matching columns equal-widthXcolumns when no explicit width is supplied.- Raw strings such as
2.5cmare passed through as LaTeX lengths.
You can force long-table rendering:
table:
long: true
long: auto is the default. placement accepts LaTeX float placement
letters such as htbp:
table:
placement: htbp
Landscape Tables¶
A table too wide for the portrait measure goes in a landscape
container. Its content is ordinary Markdown, and the container sets it on
pages of their own turned to landscape: pdflscape's landscape in LaTeX,
so a PDF viewer shows those pages rotated, and #page(flipped: true) in
Typst. The web has no pages and renders the container transparently.
::: landscape
| Entry | Location | Finding |
| ----- | -------- | ------- |
| … | … | … |
Table: Findings on the software design specification. {#tbl:sds}
:::
A long table inside turns every page it runs over. Each container starts a
page where it opens and where it closes, so it belongs at the top level of
the document, between paragraphs; the layout is the container's and not the
table's, so a wide figure or listing takes it just as well. A template
restyles it by redefining tsdiv@landscape (LaTeX) or the landscape
branch of ts-div (Typst).
Markdown Inside Cells¶
Inline Markdown survives inside cells:
columns: [Requirement, Notes]
rows:
- [REQ-001, "**Must** support `CSV` import"]
- [REQ-002, "See [the spec](./spec.md) before shipping"]
Keep the values quoted when Markdown punctuation would otherwise confuse YAML.
Plain Markdown Tables with yaml table-config¶
For small tables, pipe syntax is still the fastest input. Add a
yaml table-config fence immediately after the table to route it through the
same LaTeX table renderer:
| Abbr. | Sem. | Nom du cours | Orientations | Charge |
| ---------- | ---- | ------------------------------------- | ------------ | ------ |
| Info1 | S1 | Informatique 1 | E,M,A,N | 120 |
| MicroInfo | S1 | Microcontrôleurs et microinformatique | E,M,A,N | 120 |
| Info2 | S2 | Informatique 2 | E,M,A,N | 100 |
```yaml table-config
columns:
- {align: left}
- {align: right}
- {align: justify, width: X}
- {align: left}
- {align: right}
```
Table: Inventaire des cours d'informatique. {#tbl:cours}
columns is matched positionally against the Markdown table columns. The
example above produces a lrXlr column spec: the course-name column wraps and
absorbs the remaining line width, while the other columns keep natural width.
yaml table-config accepts:
table:
width: 100%
placement: htbp
long: auto
columns:
- {align: left}
- {align: right, width: X}
- {align: center, width-group: metrics}
It does not add grouped headers, spans, separators, or footers to a Markdown
table. Use full yaml table when the structure itself is complex.
Complete Examples¶
Grouped Financial Header¶
```yaml table
table:
width: 100%
columns:
- Product
- name: FY23
columns: [Q1, Q2, Q3, Q4]
width-group: quarter
- name: FY24
columns: [Q1, Q2, Q3, Q4]
width-group: quarter
rows:
- [Apples, [120, 135, 150, 140], [130, 145, 160, 150]]
- [Pears, [80, 90, 110, 85], [85, 95, 115, 90]]
- [Peaches, [60, 95, 110, 40], [55, 100, 120, 45]]
- separator: true
- [Cherries, [~, ~, 45, 0], [~, ~, 50, 0]]
- [Plums, [0, 0, 30, 75], [0, 0, 35, 80]]
footer:
- [Total, [260, 320, 445, 340], [270, 340, 480, 365]]
```
Table: Quarterly sales by product (2023-2024)
Three-Level Header¶
```yaml table
columns:
- Product
- name: 2024
columns:
- name: Q1
columns: [Jan, Feb, Mar]
- name: Q2
columns: [Apr, May, Jun]
rows:
- [Alpha, [10, 12, 15, 18, 20, 22]]
- [Beta, [~, ~, 5, 8, 10, 12]]
```
Table: Monthly sales breakdown for 2024
Fixed Columns plus Flexible Text¶
```yaml table
table:
width: 90%
columns:
- name: Requirement
width: 25%
align: l
- name: Description
align: j
- name: Priority
width: 15%
align: c
rows:
- [REQ-001, "The system must support CSV import with automatic delimiter detection.", High]
- [REQ-002, "PDF exports must follow the brand guidelines (logo, colours, margins).", Medium]
- [REQ-003, "The UI must be fully keyboard accessible (WCAG 2.1 AA).", High]
```
Table: Product requirements (fixed + flexible columns)
Validation Errors¶
The YAML table parser validates the table before the LaTeX renderer sees it. When something is wrong, TeXSmith emits an inline error block and keeps the document buildable.
Common failures:
- Unknown top-level keys.
- Missing
columns. - Rows with too many or too few cells.
- Unknown column names in named-row mode.
- Span rectangles whose absorbed slots are not
~. - Invalid alignment values.
- Invalid percentage widths.
Example:
```yaml table
columns:
- Year
- name: Actual
columns: [H1, H2]
rows:
- 2024: {Acutal: [6, 7]}
```
The typo Acutal is reported with the available column names. That is the
point of making complex tables data-shaped instead of pretending that pipe
tables are a database.