Tables
Alignment, spans, multi-line cells, ragged rows and the pipe rules that decide whether a table exists at all.
Generated from resources/examples/edge-cases.md and resources/examples/core.md - edit the cases there, not here. Each case links the conformance fixture it produces.
Table without alignment
1 conformance fixture
|= Name |= Age |
| Alice | 28 |
| Bob | 34 |<table>
<thead><tr><th scope="col">Name</th><th scope="col">Age</th></tr></thead>
<tbody>
<tr><td>Alice</td><td>28</td></tr>
<tr><td>Bob</td><td>34</td></tr>
</tbody>
</table>Table alignment with colspan
1 conformance fixture
|=> Category |= Item |= Price |
| Fruit | Apple | $1 |
| Total | < | $1.50 |<table>
<thead><tr><th scope="col" style="text-align: right;">Category</th><th scope="col">Item</th><th scope="col">Price</th></tr></thead>
<tbody>
<tr><td style="text-align: right;">Fruit</td><td>Apple</td><td>$1</td></tr>
<tr><td colspan="2" style="text-align: right;">Total</td><td>$1.50</td></tr>
</tbody>
</table>Table doubled alignment marker
1 conformance fixture
Per the disambiguation rule, a </>/~ immediately after | or |= is an alignment marker, and exactly one is recognized — so in |=<< the first < aligns the column left and the repeated second < is ordinary content. The marker is never doubled and never escapes the header =.
|=<< Note |= Plain |
| a | b |<table>
<thead><tr><th scope="col" style="text-align: left;">< Note</th><th scope="col">Plain</th></tr></thead>
<tbody>
<tr><td style="text-align: left;">a</td><td>b</td></tr>
</tbody>
</table>Table cell escaped pipe
1 conformance fixture
A backslash-escaped pipe is literal content and does not split the cell.
|= A |= B |
| x \| y | z |<table>
<thead><tr><th scope="col">A</th><th scope="col">B</th></tr></thead>
<tbody>
<tr><td>x | y</td><td>z</td></tr>
</tbody>
</table>Table cell pipe inside code span
1 conformance fixture
A pipe inside a code span is protected and does not split the cell.
|= A |= B |
| `a|b` | z |<table>
<thead><tr><th scope="col">A</th><th scope="col">B</th></tr></thead>
<tbody>
<tr><td><code>a|b</code></td><td>z</td></tr>
</tbody>
</table>An unclosed verbatim run in a row stops at the closing pipe
6 conformance fixtures
328-an-unclosed-verbatim-run-in-a-row-stops-at-the-closing-pipe328-an-unclosed-verbatim-run-in-a-row-stops-at-the-closing-pipe-2328-an-unclosed-verbatim-run-in-a-row-stops-at-the-closing-pipe-3328-an-unclosed-verbatim-run-in-a-row-stops-at-the-closing-pipe-4328-an-unclosed-verbatim-run-in-a-row-stops-at-the-closing-pipe-5328-an-unclosed-verbatim-run-in-a-row-stops-at-the-closing-pipe-6
A | inside an open code span is cell content, not a separator — that is what lets | a `x|` | b | be two cells. An unclosed run has no such reach: the row's own closing pipe still closes the row, and the run stops there (markup-carve/carve#1284).
| a `b | c d |<table>
<tbody>
<tr><td>a <code>b | c d</code></td></tr>
</tbody>
</table>It is a real row, so the table continues below it:
| a `b | c d |
| e | f |<table>
<tbody>
<tr><td>a <code>b | c d</code></td></tr>
<tr><td>e</td><td>f</td></tr>
</tbody>
</table>The same under a header row, since nothing here depends on the row's position:
|= h |
| a `b | c |<table>
<thead><tr><th scope="col">h</th></tr></thead>
<tbody>
<tr><td>a <code>b | c</code></td></tr>
</tbody>
</table>The run's delimiter length does not matter — an unclosed double-backtick run behaves the same:
| a ``b | c |<table>
<tbody>
<tr><td>a <code>b | c</code></td></tr>
</tbody>
</table>The control, and the reason this is a narrow rule rather than "pipes always split": a run that DOES close still hides the pipes inside it, so this row has two cells and not three.
| a `x|` | b |<table>
<tbody>
<tr><td>a <code>x|</code></td><td>b</td></tr>
</tbody>
</table>And a line with content dangling after its last pipe is still prose, open run or not — the closing-pipe requirement is unchanged.
| a | b<p>| a | b</p>A continuation row's open run, and an escaped closing pipe
5 conformance fixtures
333-a-continuation-row-s-open-run-and-an-escaped-closing-pipe333-a-continuation-row-s-open-run-and-an-escaped-closing-pipe-2333-a-continuation-row-s-open-run-and-an-escaped-closing-pipe-3333-a-continuation-row-s-open-run-and-an-escaped-closing-pipe-4333-a-continuation-row-s-open-run-and-an-escaped-closing-pipe-5
Two shapes sit either side of the row terminator, and neither was settled by the rule that an unclosed run stops at the row's closing pipe (markup-carve/carve#1293).
A + continuation extends the cell, so the block an unclosed run reaches the end of is that whole cell, continuation included. The run therefore spans the row boundary and closes on the continuation row:
| a `b |
+ c` |<table>
<tbody>
<tr><td>a <code>b c</code></td></tr>
</tbody>
</table>An escaped closing pipe is still an escape. The row closes there, because the line ends in a pipe; what the escape decides is what the CELL holds, which is a literal pipe and not an orphaned backslash:
| a b \|<table>
<tbody>
<tr><td>a b |</td></tr>
</tbody>
</table>The control that makes the asymmetry visible, and the argument that settled it: every reader already honors \| mid-cell. Reading the escape at every position except the last one is a position exception with nothing behind it, and \| is the only way to put a literal pipe in a cell:
| a \| b | c |<table>
<tbody>
<tr><td>a | b</td><td>c</td></tr>
</tbody>
</table>The continuation is cut into cells while that run is still open, which is what keeps its own pipes content. Splitting it with a fresh scanner cuts inside the run and leaves a segment with no column to join, and a dropped segment is content loss rather than a second answer:
| a `b |
+ c | d` |<table>
<tbody>
<tr><td>a <code>b c | d</code></td></tr>
</tbody>
</table>The open run belongs to ONE column, and a continuation joins per column, so the columns before it are still cut at their own pipes. Carrying the run across the whole continuation line instead swallows those separators and pushes the text into the wrong cell, which leaves the run's own cell holding an empty <code></code> - the artifact this ruling rejects, produced from the other direction:
| x | a `b |
+ y | c` |<table>
<tbody>
<tr><td>x y</td><td>a <code>b c</code></td></tr>
</tbody>
</table>Table stacked rowspan
1 conformance fixture
Consecutive ^ cells extend the same origin cell; two stacked ^ markers produce rowspan="3".
|= Tier |= User |
| Gold | Ann |
| ^ | Bo |
| ^ | Cy |<table>
<thead><tr><th scope="col">Tier</th><th scope="col">User</th></tr></thead>
<tbody>
<tr><td rowspan="3">Gold</td><td>Ann</td></tr>
<tr><td>Bo</td></tr>
<tr><td>Cy</td></tr>
</tbody>
</table>Table rowspan with multi-line content
1 conformance fixture
A + continuation before a ^ rowspan extends the spanned cell.
|= Category |= Item |
| Fresh Fruits | Apple |
+ from local | |
+ farms | |
| ^ | Banana |<table>
<thead><tr><th scope="col">Category</th><th scope="col">Item</th></tr></thead>
<tbody>
<tr><td rowspan="2">Fresh Fruits from local farms</td><td>Apple</td></tr>
<tr><td>Banana</td></tr>
</tbody>
</table>Superscript in a table cell
2 conformance fixtures
Superscript in a cell uses the braced form {^…^}. A lone ^ as the sole cell content is a rowspan marker; any other bare ^ in a cell is literal text.
| Value |
| {^2^} |<table>
<tbody>
<tr><td>Value</td></tr>
<tr><td><sup>2</sup></td></tr>
</tbody>
</table>| Value |
| ^2^ |<table>
<tbody>
<tr><td>Value</td></tr>
<tr><td>^2^</td></tr>
</tbody>
</table>Table span marker in first column
1 conformance fixture
A span marker (^ rowspan / < colspan) must be the whole cell. In the first column a < (or in the first row a ^) has nothing to merge into, so it renders as an empty cell rather than being dropped.
| < | b |
|---|---|
| c | d |<table>
<thead><tr><th scope="col"></th><th scope="col">b</th></tr></thead>
<tbody>
<tr><td>c</td><td>d</td></tr>
</tbody>
</table>Table cell attributes
1 conformance fixture
A {…} attribute block glued to a cell (no space) sets that cell's attributes; the rest, after optional whitespace, is the cell content. A space before the brace keeps it literal, and a cell carrying attributes is never a bare span marker. The block binds after the cell's kind and alignment markers, so on a cell with no marker it sits directly against the opening |.
|{.highlight} Total | 99 |
|---|---|
| a | b |<table>
<thead><tr><th scope="col" class="highlight">Total</th><th scope="col">99</th></tr></thead>
<tbody>
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>Cell attributes bind after the kind and alignment markers
6 conformance fixtures
319-cell-attributes-bind-after-the-kind-and-alignment-markers319-cell-attributes-bind-after-the-kind-and-alignment-markers-2319-cell-attributes-bind-after-the-kind-and-alignment-markers-3319-cell-attributes-bind-after-the-kind-and-alignment-markers-4319-cell-attributes-bind-after-the-kind-and-alignment-markers-5319-cell-attributes-bind-after-the-kind-and-alignment-markers-6
A cell's {…} attribute block attaches AFTER the kind marker = and after the alignment marker, in every cell. The block is glued to the marker run where the cell has one and to the opening | where it has none; a space in front of it still keeps it literal. This is what makes an attributed HEADER cell expressible: with the block bound ahead of the =, the only available shape is |{#x}=R|, which reads as a data cell whose content starts with =.
|={.total} Total |= 99 |
| a | b |<table>
<thead><tr><th scope="col" class="total">Total</th><th scope="col">99</th></tr></thead>
<tbody>
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>Both markers may precede the block. The kind marker comes first, then the alignment marker, then the attributes; a native alignment marker on a header cell still sets the whole column's alignment.
|=~{#score} Score |
| 9 |<table>
<thead><tr><th scope="col" id="score" style="text-align: center;">Score</th></tr></thead>
<tbody>
<tr><td style="text-align: center;">9</td></tr>
</tbody>
</table>On a data cell the alignment marker likewise comes first, and the computed alignment composes with the authored block rather than replacing it.
|= Item |= Cost |
| Pen |>{.num} 9 |<table>
<thead><tr><th scope="col">Item</th><th scope="col">Cost</th></tr></thead>
<tbody>
<tr><td>Pen</td><td class="num" style="text-align: right;">9</td></tr>
</tbody>
</table>The other order is no longer a marker position. A < written AFTER the attribute block is ordinary content, so the cell carries the attributes and is not aligned. This is the released spelling that changes meaning, and it reinterprets rather than erroring.
|{#x}< content |<table>
<tbody>
<tr><td id="x">< content</td></tr>
</tbody>
</table>The shape that used to be the only candidate for an attributed header cell is still a data cell, which is the ambiguity the rule removes: the = is content because the block ahead of it already committed the cell.
|{#x}=R|<table>
<tbody>
<tr><td id="x">=R</td></tr>
</tbody>
</table>Row attributes do not move. They stay glued to the row's closing |, and they compose with cell attributes written in the new position.
|=<{.h} Name |=>{.c} Score |{.head}
| Ann |>{.num} 9 |{.win}<table>
<thead><tr class="head"><th scope="col" class="h" style="text-align: left;">Name</th><th scope="col" class="c" style="text-align: right;">Score</th></tr></thead>
<tbody>
<tr class="win"><td style="text-align: left;">Ann</td><td class="num" style="text-align: right;">9</td></tr>
</tbody>
</table>Table row attributes
1 conformance fixture
An attribute block glued to a row's closing | sets that row's <tr> attributes - the row-level twin of a cell's attribute block, in a different position: a row's follows the row's last |, a cell's follows the cell's markers. It applies to a header or a body row and composes with the GFM delimiter row.
| Name | Score |{.head}
|------|-------|
| Ann | 9 |{.win}<table>
<thead><tr class="head"><th scope="col">Name</th><th scope="col">Score</th></tr></thead>
<tbody>
<tr class="win"><td>Ann</td><td>9</td></tr>
</tbody>
</table>Table header cell rowspan
1 conformance fixture
A ^ rowspan marker extends the cell above it even across the header/body boundary: a header cell can span into the body rows below, rendering as <th scope="col" rowspan="N">.
|= H |= G |
| ^ | b |
| ^ | c |<table>
<thead><tr><th scope="col" rowspan="3">H</th><th scope="col">G</th></tr></thead>
<tbody>
<tr><td>b</td></tr>
<tr><td>c</td></tr>
</tbody>
</table>Blocked span marker renders as empty cell
1 conformance fixture
A span marker merges into the nearest still-available origin: a ^ walks up its column, a < walks left along its row, skipping cells already consumed by another span. When the walk reaches no available cell at all - it runs off the edge of the table - the marker is neither dropped nor left literal: it renders as an EMPTY cell (<td></td>) carrying no content and no span. The first-row ^ / first-column < orphan is one instance (see "Table span marker in first column"); the same rule covers a marker BLOCKED when every cell back to the edge is already consumed.
Here the second body row leads with ^, so the x above it gains rowspan="2". The next cell is <; its only left neighbor (the first column) is now occupied by that rowspan, so the leftward walk runs off the edge with nothing to merge and the < becomes an empty cell. The trailing d follows as usual.
| A | B | C |
|---|---|---|
| x | y | z |
| ^ | < | d |<table>
<thead><tr><th scope="col">A</th><th scope="col">B</th><th scope="col">C</th></tr></thead>
<tbody>
<tr><td rowspan="2">x</td><td>y</td><td>z</td></tr>
<tr><td></td><td>d</td></tr>
</tbody>
</table>Colspan marker scans left past a consumed cell
1 conformance fixture
The same leftward walk SUCCEEDS when an available cell sits beyond the consumed columns: a < skips every column already taken by another span and merges into the nearest cell that is still free, only falling back to an empty cell when the walk reaches the table edge with nothing to merge.
Here the second body row is | p | ^ | < | e |. The ^ (column 2) continues the rowspan of b directly above it, so column 2 is consumed and b gains rowspan="2". The < (column 3) then walks left, skips that consumed column, and merges into p (column 1), so p gains colspan="2". The trailing e follows as a plain cell.
The walk counts the consumed column toward the span it grows, so the resulting colspan can visually overlap the cell occupying that column (here p's colspan="2" covers the column b's rowspan still holds). That overlap is the defined result of the walk-and-merge model, not an error: span markers only ever grow an existing cell or, when blocked at the edge, become an empty cell - the author chooses the layout by where they place the markers.
| p | q | r | s |
|---|---|---|---|
| a | b | c | d |
| p | ^ | < | e |<table>
<thead><tr><th scope="col">p</th><th scope="col">q</th><th scope="col">r</th><th scope="col">s</th></tr></thead>
<tbody>
<tr><td>a</td><td rowspan="2">b</td><td>c</td><td>d</td></tr>
<tr><td colspan="2">p</td><td>e</td></tr>
</tbody>
</table>A pipe pair with no cell is not a table
1 conformance fixture
|| has no cell between the pipes, so it is ordinary paragraph text, not a one-cell table.
||<p>||</p>A continuation row needs a body row
1 conformance fixture
A + continuation row joins the row ABOVE IT IN THE SOURCE, and a delimiter row is consumed: it produces no <tr> and is not a row of the table. So a + line directly under one has no row to join and stays an ordinary paragraph.
The reason is the delimiter row and not the missing body row. A header row on its own IS joinable - see A continuation row joins the row above it, whatever its cells hold below - so "no body row yet" would answer that document wrongly (markup-carve/carve#1354).
| a | b |
| - | - |
+ cont |<table>
<thead><tr><th scope="col">a</th><th scope="col">b</th></tr></thead>
</table>
<p>+ cont |</p>Table row closing pipe
3 conformance fixtures
A table row must close with a pipe. A line that starts with | but has content dangling after its last pipe is prose, wherever it appears - it neither opens a table at a block start nor interrupts an open paragraph.
| a | b<p>| a | b</p>The rule applies to every row, not only the first: once a line fails to close, the table ends and that line is a paragraph.
| a | b |
| c | d<table>
<tbody>
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>
<p>| c | d</p>And an unclosed line does not swallow a following well-formed row: the row still opens its own table.
| a
| b |<p>| a</p>
<table>
<tbody>
<tr><td>b</td></tr>
</tbody>
</table>A table delimiter cell needs at least one dash
1 conformance fixture
A lone : does not make a delimiter row, so the row is an ordinary body row and the table gets no header.
| a | b |
|:|:|
| 1 | 2 |<table>
<tbody>
<tr><td>a</td><td>b</td></tr>
<tr><td>:</td><td>:</td></tr>
<tr><td>1</td><td>2</td></tr>
</tbody>
</table>A continuation row carries no trailing text
2 conformance fixtures
The + continuation marker replaces the leading pipe, and the row must close with one. A proper continuation merges into the row above; text after the closing pipe leaves the line outside the table entirely.
| a | b |
+ c | d |<table>
<tbody>
<tr><td>a c</td><td>b d</td></tr>
</tbody>
</table>| a | b |
+ c | d | junk<table>
<tbody>
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>
<p>+ c | d | junk</p>A ragged table keeps each row's cell count
3 conformance fixtures
Cell count is content, not source padding. The canonical writer can align the cells a row carries, but it does not append empty cells to make the table rectangular. A missing trailing cell and an authored empty cell render as different table structures.
| ~x~ |
| a | b |<table>
<tbody>
<tr><td><s>x</s></td></tr>
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>The same rule applies when the first row is a header. A short body row stays short, and the canonical native = spelling does not need a delimiter row.
| |x |
|---|
| y |<table>
<thead><tr><th scope="col"></th><th scope="col">x</th></tr></thead>
<tbody>
<tr><td>y</td></tr>
</tbody>
</table>A header row may itself be shorter than a later body row. Its native markers still preserve it as a header without manufacturing another header cell.
| h |
|---|
| |x |<table>
<thead><tr><th scope="col">h</th></tr></thead>
<tbody>
<tr><td></td><td>x</td></tr>
</tbody>
</table>Tables
2 conformance fixtures
An escaped pipe inside cell content (\|) renders as a literal | and does not split the cell.
|= Symbol |= Meaning |
| \| | pipe char |<table>
<thead><tr><th scope="col">Symbol</th><th scope="col">Meaning</th></tr></thead>
<tbody>
<tr><td>|</td><td>pipe char</td></tr>
</tbody>
</table>Empty cells produce empty <td> elements — placement is preserved, not collapsed.
|= A |= B |= C |
| 1 | | 3 |<table>
<thead><tr><th scope="col">A</th><th scope="col">B</th><th scope="col">C</th></tr></thead>
<tbody>
<tr><td>1</td><td></td><td>3</td></tr>
</tbody>
</table>