In the CMS a table is built with Insert Table and edited through the table menu, with Tab and Shift-Tab moving between cells. This page is the markdown underneath — which matters more here than on most pages in this category, because tables are where the site renderer and the editor disagree most sharply, and the disagreement is silent.
Basic table
| Header 1 | Header 2 |
| -------- | -------- |
| Cell | Cell |
| Cell | Cell |How it renders
| Header 1 | Header 2 |
|---|---|
| Cell | Cell |
| Cell | Cell |
The first row is the header, the dashed row separates it from the body, and every row after that is data. The dashes do not have to line up with anything and the pipes do not have to be padded — |Header 1|Header 2| parses identically. Aligning the source is purely for whoever reads the markdown next.
Column alignment
Colons in the delimiter row set a column’s alignment. It is emitted as an inline style on every th and td in that column, so it survives into any stylesheet without needing a class.
| Left | Right | Center |
| :--- | ----: | :----: |
| data | data | data |How it renders
| Left | Right | Center |
|---|---|---|
| data | data | data |
| Marker | Alignment | Survives a save? |
|---|---|---|
:--- | left | No |
---: | right | No |
:---: | center | No |
Formatting inside cells
Cells take inline formatting — marks, code, links and icons all work:
| **Bold** | *Italic* | `Code` | [Link](https://leed.ai) |
| -------- | -------- | ------ | ----------------------- |
| Data | Data | Data | Data |How it renders
| Bold | Italic | Code | Link |
|---|---|---|---|
| Data | Data | Data | Data |
What a cell cannot contain is a block: no paragraphs, no lists, no fenced code, no line breaks. A cell is one line of inline content, and a pipe inside it has to be escaped as \|.
Table attributes
Attributes for the whole table go on a line of their own, directly after the last row with no blank line between them:
| Plan | Price |
| ----- | ----- |
| Basic | $10 |
{id="pricing"}That line is parsed as a table row, recognized as containing nothing but attributes, applied to the <table> element and then removed from the token stream — which is why it does not show up as an empty row on the published page.
:::warning {#pricing} sets nothing here The attribute line after a table is read by a strict parser that matches only key="value" with double quotes. {#pricing} and {.wide} are discarded. The line still disappears, so the page looks correct and the id you were going to link to simply is not there. Write {id="pricing"} and {class="wide"}. :::
Every quoted pair on that line lands on the <table>, not only id, class and data-* — {role="grid"} reaches the element too.
Cell attributes
A single cell takes attributes at the end of its own content. These come from the general attributes plugin rather than the strict line parser, so shorthand does work inside a cell:
| Item | Q1 | Q2 |
| :--------------------------- | ---: | ---: |
| Combined total {colspan="2"} | 41 |
| Rolling {rowspan="2"} | 12 | 19 |
| 14 | 22 |
| Regional {.muted} | 8 | 11 |How it renders
| Item | Q1 | Q2 |
|---|---|---|
| Combined total | 41 | |
| Rolling | 12 | 19 |
| 14 | 22 | |
| Regional | 8 | 11 |
{colspan="2"}makes the cell span two columns; the row then carries one fewer cell than the header.{rowspan="2"}makes the cell span down into the next row; that next row carries one fewer cell, and the cells you do write fill the columns the span leaves free.{.muted}puts a class on that one cell. Cell attributes stay on the cell — they are not promoted to the table.
| Target | Where you write it | Shorthand? | Lands on |
|---|---|---|---|
| Whole table | its own line, directly after the last row | No — key="value" only | the <table> |
| One cell | at the end of that cell’s content | Yes | that <td> or <th> |
The three-parser split that this table is an instance of is explained on Attributes; it is the same rule that governs lists and blockquotes.
What the editor does to a table
Three things. Each is silent, and each has a different shape.
Alignment is lost on every save
Covered above, and repeated here because it is the one that costs real work: the serializer writes a fixed dashes-only delimiter row, so column alignment authored anywhere other than the editor is gone the first time the page is re-serialized.
Cell spans are markdown-only
colspan and rowspan render correctly on the published site, there is no control for them in the table menu, and the serializer does not write them back. A merged table authored in markdown and then opened in the CMS comes back as a plain grid — with the spanned rows now short a cell, which is worse than losing the merge alone. Reserve spans for content that will never be edited in the CMS.
A trailing attribute line grows an empty row
Round-tripping a table that carries an attribute line — {data-id="t1"}, say — adds an all-empty row to the stored markdown, and it does so on each pass. The published page is unaffected, because the build strips trailing all-empty rows before emitting the <table>, but the source file accumulates them. If a table’s markdown looks like it has grown a tail of | | | rows, this is why, and deleting them is safe.
All three, and everything else that changes on a save, are collected on Fidelity and Unsupported Syntax.
Which page types allow tables
The table toggle is one of nine formatting switches on a page type, and it applies to posts-type page types only — blogs and similar. On a documentation or api page type the toggle does nothing, there is no control for it in Settings, and every formatting feature is available. If you are writing documentation and hunting for a switch to turn tables on, there is not one. What Each Page Type Lets You Format has the full picture, and inserting and editing a table in the CMS is Block Types and Block Settings.