Markdown Tables: Alignment, Escaping, and the Rules That Matter
How GitHub-flavored pipe tables really work: the delimiter row, colon alignment, escaping pipes in code spans, and where tables run out of road.
Nobody hand-writes a wide Markdown table twice. The first one you type out carefully, counting pipes as you go. The second one you paste from a spreadsheet and then spend ten minutes nudging into shape.
Which is a bit silly, because the syntax underneath is tiny. Most of the pain comes from three or four rules that nothing tells you about until you hit them.
The delimiter row is the table
A table is a header line, a line of dashes, and then body rows:
| Package | Version | Notes |
| --- | --- | --- |
| astro | 5.1.1 | static build |
Delete the dashes line and you no longer have a table. You have three lines of text with pipes in them, rendered exactly like that. It’s the single most common reason a table shows up as plain text in a pull request.
That header row also fixes the column count permanently. The GFM spec is blunt about what happens next: body rows with fewer cells get empty ones appended, and rows with more cells have the extras discarded. Silently. No warning anywhere. A row just quietly loses its last column, and you notice three commits later.
Alignment is colons, nothing else
The dashes row carries alignment for the whole column:
:---left:---:centered---:right---whatever the renderer’s default is, usually left
There’s no way to align a single cell differently from its column, and no way to align a whole table on the page. If you need either, you’re writing HTML, not Markdown. That’s not a limitation people run into often, but it does surprise anyone coming from a word processor.
The Markdown Table Generator puts a small selector above each column, so you set alignment by picking it rather than by remembering which side the colon goes on.
The spaces are for you, not for the renderer
Padded tables look like this:
| Package | Version | Notes |
| ----------- | ------- | -------------- |
| astro | 5.1.1 | static build |
Every renderer trims whitespace inside cells before doing anything else, so the padding changes nothing about the output. It exists purely so the raw file is readable when someone opens it in an editor. On a table with twelve columns the padding roughly doubles the file size and makes every future diff noisier, which is why some teams turn it off. Both forms render identically, so it’s a taste call.
Pipes, line breaks, and other sharp edges
A pipe inside cell text ends the cell. Escape it as \| and it renders as a normal character. The part that catches people out: this applies inside code spans too. Writing `a|b` in a table splits the cell anyway, backticks or not, and you have to write `a\|b` instead. The GFM spec calls this out explicitly, which suggests plenty of people have filed the bug.
Cells hold inline content only. Bold, links, images, inline code, fine. Lists, headings, fenced code blocks, no. And a row must stay on one physical line, so a multi-line cell means a literal <br> tag. Ugly, universally supported, and the only option.
Build the table in a grid, let the tool handle the pipes and the escaping, and paste the result where you need it. Try it here — everything runs in your browser, nothing gets uploaded.