Tables
Tables in Markdown: pipes and the delimiter row, column alignment, what you can and cannot put in a cell, and when a table is the wrong tool.
A first table
A Markdown table is a grid of
rows and cells written as lines of text, with a pipe character
(|) between the cells. The first line holds the column
headings, the second line is a row of dashes that marks the lines
above and below as a table, and every following line is a data row.
The renderer turns it into an
HTML table.
| Host | Role | Location |
| ----- | -------------- | ------------------ |
| web01 | Portal (front) | Cloud (eu-west) |
| web02 | Portal (front) | Cloud (eu-central) |
| db02 | Billing DB | Rotterdam |
<table> <thead> <tr> <th>Host</th> <th>Role</th> <th>Location</th> </tr> </thead> <tbody> <tr> <td>web01</td> <td>Portal (front)</td> <td>Cloud (eu-west)</td> </tr> <tr> <td>web02</td> <td>Portal (front)</td> <td>Cloud (eu-central)</td> </tr> <tr> <td>db02</td> <td>Billing DB</td> <td>Rotterdam</td> </tr> </tbody> </table>
The header row becomes <thead> with
<th> cells, the data rows go into
<tbody>. The result frame shows the browser's
default style: no borders. On GitHub, documentation sites and this
encyclopedia, the site's stylesheet adds lines and spacing.
Pipes and spacing
The pipes at the start and end of each row are optional, and the columns do not have to line up in the source. Both tables below give the same HTML:
Host | Role
--- | ---
web01 | Portal (front)
| Host | Role |
|-------|----------------|
| web01 | Portal (front) |
<table> <thead> <tr> <th>Host</th> <th>Role</th> </tr> </thead> <tbody> <tr> <td>web01</td> <td>Portal (front)</td> </tr> </tbody> </table> <table> <thead> <tr> <th>Host</th> <th>Role</th> </tr> </thead> <tbody> <tr> <td>web01</td> <td>Portal (front)</td> </tr> </tbody> </table>
Still, write the outer pipes and line the columns up when the table is small enough. The point of Markdown is that the source is readable as plain text, and an aligned table is much easier to read and to review in a pull request. Many editors and formatters can align a table for you, so you do not have to count spaces by hand.
The delimiter row
The delimiter row is the line of dashes directly under the header row. It is what makes the lines a table: one cell of dashes (at least one dash, usually three) for every column, separated by pipes, optionally with colons for alignment. It produces no visible row of its own.
Two rules decide whether a table is recognized at all:
- A header row is required. A Markdown table always starts with one row of headings. There is no table without headings, which is good for accessibility: every column gets a header cell.
- The header row and the delimiter row must have the same number of cells. If they differ, the renderer does not see a table.
| Host | Role | Location |
| ----- | -------------- |
| web01 | Portal (front) | Cloud (eu-west) |
<p>| Host | Role | Location | | ----- | -------------- | | web01 | Portal (front) | Cloud (eu-west) |</p>
The delimiter row has two cells for three headings, so everything stays one paragraph of text with pipes in it. Data rows are more forgiving: a row with too few cells is filled up with empty cells, and cells beyond the number of columns are dropped.
| Host | Role | Location |
| ----- | -------------- | --------- |
| web03 | Spare |
| db02 | Billing DB | Rotterdam | moved in 2025 |
<table> <thead> <tr> <th>Host</th> <th>Role</th> <th>Location</th> </tr> </thead> <tbody> <tr> <td>web03</td> <td>Spare</td> <td></td> </tr> <tr> <td>db02</td> <td>Billing DB</td> <td>Rotterdam</td> </tr> </tbody> </table>
Column alignment
Column alignment in a
Markdown table is set with colons in the delimiter row: a colon on
the left (:---) aligns the column left, on the right
(---:) aligns it right, on both sides
(:---:) centers it. Without colons, the renderer adds
nothing and the site's stylesheet decides, which usually means left
for data cells.
| Tariff | Price per kWh | Green |
| :------------- | ------------: | :---: |
| Fixed, 1 year | €0.27 | yes |
| Variable | €0.25 | no |
| Dynamic | €0.22 | yes |
<table> <thead> <tr> <th style="text-align:left">Tariff</th> <th style="text-align:right">Price per kWh</th> <th style="text-align:center">Green</th> </tr> </thead> <tbody> <tr> <td style="text-align:left">Fixed, 1 year</td> <td style="text-align:right">€0.27</td> <td style="text-align:center">yes</td> </tr> <tr> <td style="text-align:left">Variable</td> <td style="text-align:right">€0.25</td> <td style="text-align:center">no</td> </tr> <tr> <td style="text-align:left">Dynamic</td> <td style="text-align:right">€0.22</td> <td style="text-align:center">yes</td> </tr> </tbody> </table>
Right-align columns of numbers and amounts, so the digits line up and values are easy to compare. Center short values such as "yes"/"no" or status symbols if you like; leave text left-aligned. The colons only set alignment: they do not affect column width, which the browser and the site's stylesheet decide.
What fits in a cell
A cell holds exactly one line of inline content: text, emphasis, links and images, and inline code. Spaces around the content are trimmed.
| Check | Command | Runbook |
| -------------- | ------------------------- | -------------------------------- |
| Portal service | `systemctl status portal` | [Restart](runbook.md#restart) |
| Disk space | `df -h /var/log` | **Escalate** to NOC above 85% |
<table> <thead> <tr> <th>Check</th> <th>Command</th> <th>Runbook</th> </tr> </thead> <tbody> <tr> <td>Portal service</td> <td><code>systemctl status portal</code></td> <td><a href="runbook.md#restart">Restart</a></td> </tr> <tr> <td>Disk space</td> <td><code>df -h /var/log</code></td> <td><strong>Escalate</strong> to NOC above 85%</td> </tr> </tbody> </table>
What does not fit:
- Block content. Lists, headings, code blocks, quotes and multiple paragraphs cannot go in a cell. A row ends at the end of the line, so a list item on the next line ends the table instead.
-
Line breaks. There is no Markdown way to break a
line inside a cell. Platforms that allow raw HTML, such as GitHub,
accept
<br>; elsewhere it shows up as text. -
Merged cells. Markdown has no equivalent of
colspanorrowspan. A table that needs them should be split, or written in HTML where the platform allows it. - A caption. There is no syntax for a table caption. Put a sentence or a heading directly above the table that says what it shows.
A pipe inside a cell
Because | separates cells, a literal pipe must be
escaped as \|. This also applies inside inline code in
a table, which is unusual: elsewhere, code never needs escaping. It
matters for IT tables, where commands with pipes are common.
| Task | Command |
| ---------------- | ------------------------------ |
| Biggest log dirs | `du -sh /var/log/* \| sort -h` |
| Broken | `ps aux | grep portal` |
<table> <thead> <tr> <th>Task</th> <th>Command</th> </tr> </thead> <tbody> <tr> <td>Biggest log dirs</td> <td><code>du -sh /var/log/* | sort -h</code></td> </tr> <tr> <td>Broken</td> <td>`ps aux</td> </tr> </tbody> </table>
In the second row the unescaped pipe split the command into two cells before the code could close: the first cell shows a stray backtick and no code, and the second half was dropped as an extra cell. The backslash before the pipe in the first row does not appear in the result.
Where tables break
A table ends at the first blank line, or where another block (a heading, a list, a quote) starts. A line of ordinary text directly under a table is not the end: it becomes one more row. Leave a blank line before and after every table.
| Host | Role |
| ----- | -------------- |
| web01 | Portal (front) |
Last checked by Emeka Obi on 2 October.
<table> <thead> <tr> <th>Host</th> <th>Role</th> </tr> </thead> <tbody> <tr> <td>web01</td> <td>Portal (front)</td> </tr> <tr> <td>Last checked by Emeka Obi on 2 October.</td> <td></td> </tr> </tbody> </table>
The sentence ended up in the first column as an extra row. With a blank line between the table and the sentence, it becomes a normal paragraph. A blank line before the table is the safe choice too: renderers do not all agree on whether a table may follow a paragraph line directly, and the blank line keeps the source readable.
Pipe tables and Markdown flavors
The syntax on this page is the pipe table: the table style with pipes between the cells and a delimiter row of dashes. It was not in the original Markdown and is not part of CommonMark. It is one of the five extensions that GitHub Flavored Markdown adds to CommonMark. Some converters support other table syntaxes as well; the pipe style is the one that works almost everywhere tables work at all.
Where you will and will not get a table, as of 2026:
| Where | Pipe tables |
|---|---|
| GitHub, GitLab (files, issues, pull requests) | Yes |
| markdown-it with default settings (this site) | Yes |
| A strict CommonMark renderer | No: the lines stay text with pipes |
| Slack messages (mrkdwn) | No |
| Microsoft Teams chat | Not in Microsoft's list of supported Markdown |
Here is the first table of this page as a strict CommonMark renderer shows it. This site's renderer does support tables, so the result below is described, not run:
<p>| Host | Role | Location | | ----- | -------------- | ------------------ | | web01 | Portal (front) | Cloud (eu-west) | …</p>
Pasting a table into a chat that does not support it gives the reader a block of pipes and dashes. In that case, send a short list instead, or share the table as a file or a link. More about chat tools in Markdown in Chat, Notes and AI Tools.
When a table is the wrong tool
A Markdown table is good for short, regular data: a few columns of short values that readers compare across rows. Hosts and their roles, tariffs and prices, a contact list for an on-call rotation. It becomes a burden when:
- Cells hold sentences. Long text makes the source lines hundreds of characters wide, impossible to read in plain text and painful to review in a diff. On a phone, the rendered table is squeezed or scrolls sideways.
- Only two columns hold "name" and "explanation". That is a list, or a series of short headings with a paragraph each.
- The content needs structure inside cells, such as steps or code blocks. Use headings and lists.
- The table is used for layout, to place things side by side. Markdown is for content; the layout belongs to the site.
## Escalation
- **Network outage**: call the NOC on-call engineer (Tomasz Nowak
this week). Have the affected area and the start time ready.
- **Portal down**: page the portal team through the alerting tool,
then post in the incident channel.
<h2>Escalation</h2> <ul> <li><strong>Network outage</strong>: call the NOC on-call engineer (Tomasz Nowak this week). Have the affected area and the start time ready.</li> <li><strong>Portal down</strong>: page the portal team through the alerting tool, then post in the incident channel.</li> </ul>
As a table, this would have two columns with a full sentence in each cell. As a list, it reads well in both the source and the result, and each item can grow without breaking the layout.