GitHub Markdown Tables: Syntax, Alignment, and Formatting
How to write a GitHub markdown table — pipe syntax, alignment colons, escaping pipes, what renders inside cells, and GitHub's limits on HTML and merged cells.
Short answer: A GitHub markdown table is a header row, a separator row of hyphens, and any number of data rows, all divided by pipe characters. Add colons to the separator row for alignment: :--- left, :---: center, ---: right. Leading and trailing pipes are optional, columns don't need to line up in the source, and a literal pipe inside a cell must be escaped as \|.
| Package | Version | Status |
|:--------|:-------:|-------:|
| core | 2.1.0 | stable |
| beta | 0.4.2 | preview |
Tables are the most-used GitHub-flavored markdown extension and the one with the most surprising edges. They're in every README comparison matrix, every issue template, every API reference. This guide covers the syntax GitHub actually implements, the escaping rule that breaks tables silently, and the things people keep trying that GitHub's HTML sanitizer will not let them do. For markdown tables in general, across other flavors, see our markdown tables syntax guide.
The Rules GitHub Actually Enforces
GitHub implements the GFM spec, which is stricter than most people realize in a couple of places and looser in others.
The header row is mandatory. GFM has no headerless table. If you don't want visible headers, leave the cells empty — the header row still has to be there:
| | |
|---|---|
| Key | Value |
| Another | Value |
The separator row needs at least one hyphen per cell. |---|---| and |-|-| are both valid. Three hyphens is convention, not a requirement.
The separator row's cell count must match the header's. If it doesn't, GitHub doesn't recognize a table at all — you get a paragraph full of pipes. This is the single most common reason a table "doesn't work."
Body rows can have the wrong cell count and the table still renders. Extra cells past the header count are discarded. Missing cells are filled in as empty. That's convenient and also dangerous — a row silently losing its last column is a bug you'll only notice by reading the rendered output.
Leading and trailing pipes are optional. All three of these produce the same table:
| A | B |
|---|---|
| 1 | 2 |
A | B
--- | ---
1 | 2
| A | B
|---|---
| 1 | 2
The first form is worth using anyway. It makes the column boundaries obvious in the source and it's what every other README does.
Put a blank line before the table. If a table follows a paragraph with no blank line between them, GitHub will often fold the first row into that paragraph instead of starting a table. Same for the line after — the table ends at the first blank line or the start of another block.
Alignment
Colons in the separator row set per-column alignment:
| Left | Center | Right | Default |
|:-----|:------:|------:|---------|
| a | b | c | d |
| longer text | more | 1,234.00 | text |
| Marker | Alignment |
|---|---|
:--- | Left |
:---: | Center |
---: | Right |
--- | Default (left) |
Right alignment is the one that earns its keep. Numeric columns — versions, byte counts, prices, durations — are far easier to compare when the digits line up on the right edge. Center alignment is best reserved for short status values and single characters; centered prose is hard to scan.
One thing alignment does not do: change column width. GitHub sizes columns from their content. :---: centers text inside whatever width the column ends up with. There is no markdown syntax for setting a column width on GitHub, and the HTML route is blocked (more on that below).
Wide tables scroll horizontally in GitHub's rendered output rather than compressing into unreadable slivers. That's better than the alternative, but it also means a twelve-column table is effectively invisible past column six on a laptop and past column three on a phone.
Escaping Pipes (The One That Breaks Silently)
A literal | inside a cell ends the cell. To keep it, escape it with a backslash:
| Command | Description |
|---------|-------------|
| `ls \| wc -l` | Count files |
| `a \|\| b` | Logical or |
The critical detail: you have to escape the pipe even inside a code span. GitHub splits the row into cells before it parses inline markdown, so backticks give you no protection. This is wrong:
| `ls | wc -l` | Count files |
That produces three cells — `ls, wc -l`, and Count files — and mangles the code span across two of them. The table still renders, which is why the bug survives review.
The HTML entity | works too, and some people prefer it because it doesn't look like an escape sequence when read as raw text:
| `ls | wc -l` | Count files |
Either is fine. Pick one and be consistent. If you're documenting shell pipelines, regular expressions, or union types, you will use this constantly.
What Renders Inside a Cell
Inline formatting works normally:
| Item | Notes |
|------|-------|
| **Bold label** | Regular text |
| `code span` | Monospaced |
| [Link text](https://example.com) | Autolinked |
| ~~Removed~~ | Strikethrough |
|  | Images work |
| Fixed in #142 by @octocat | GitHub autolinks work |
GitHub's autolinks are live inside table cells, so #142 links to the issue and @octocat links to the user, exactly as they would in prose. Emoji shortcodes work too. Images render inline and are proxied through GitHub's image proxy, so external images load over HTTPS regardless of the source URL.
What does not work inside a cell:
- Multi-line content. A cell is one line of source. Use
<br>where you need a line break. - Block elements. No headings, no nested tables, no blockquotes, no fenced code blocks. A fenced block needs its own lines; a table row has one. Use inline code spans instead.
- Lists. No
-or1.list rendering inside a cell.<br>plus a bullet character (•) is the usual workaround, and it's ugly enough that it should make you reconsider the table. - Task list checkboxes.
- [ ]is a list feature. Inside a table cell you get literal brackets, not a checkbox.
The <br> workaround in practice:
| Option | Description |
|--------|-------------|
| `--verbose` | Print detailed output.<br>Repeat for more detail. |
| `--quiet` | Suppress all non-error output. |
Merged Cells, Colspans, and GitHub's Sanitizer
There is no markdown syntax for merged cells, and dropping to raw HTML mostly doesn't rescue you.
GitHub sanitizes all HTML in markdown against an allowlist. <table>, <tr>, <td>, <th>, and <br> survive. <script> and <style> do not. Critically, style and class attributes are stripped, so even a hand-written HTML table gives you no control over widths, colors, borders, or padding. You trade readable markdown source for verbose HTML and get nothing back visually.
The honest advice: if your table needs merged cells, it probably shouldn't be a table in a README. Split it into two tables, or use headings and short lists, or render an image of the real table and link to the source data.
The same goes for anything else you might want to style. GitHub's rendering of tables is fixed. That's a deliberate constraint — it's what makes every README on the site look consistent — and fighting it is a poor use of an afternoon.
Where Tables Render on GitHub (And Where They Don't)
| Surface | Tables render? |
|---|---|
README.md and other .md files in a repo | Yes |
| Issue and PR descriptions | Yes |
| Issue, PR, and commit comments | Yes |
| PR review comments (inline on the diff) | Yes |
| Discussions | Yes |
| Wikis | Yes |
Gists (.md files) | Yes |
| Release notes | Yes |
| Commit messages | No — plain text with autolinks only |
| Code comments in source files | No |
That last pair catches people. A beautifully formatted table in a commit message renders as raw pipes in the commit log, forever. Put it in the PR description instead.
One more renderer to watch: if the repository is also published through GitHub Pages, those pages are built by Jekyll, whose default markdown processor is not GitHub's own. Pipe tables and alignment colons still work, but edge cases can differ from the repo view. If a table looks right on github.com and wrong on your project site, check your Pages markdown processor before rewriting the table.
Generating and Maintaining Tables
Hand-aligning pipes is tedious, and every edit to one cell breaks the alignment of the whole column. Some options:
Let the source be ragged. GitHub renders |a|b| and | a | b | identically. Ragged source is legitimate and it makes diffs smaller, because changing one cell doesn't reformat the rest of the row. The cost is that the raw file is harder to read.
Format on save. Most markdown editors can realign a table's pipes for you. In OpenMark, ⌘⇧T reformats the table under the cursor, padding every cell so the columns line up in the source, and Tab moves between cells. You get readable source without counting spaces.
Generate from data. If the contents come from a script, a config file, or a spreadsheet, emit the markdown from that data rather than maintaining both copies by hand.
Check the rendered output. Because a cell-count mismatch renders as a broken-but-visible table, review it rendered rather than in the diff. OpenMark's Document view shows the rendered table as you'd see it on GitHub, and its Markdown view shows the source with syntax coloring — switching between them catches the escaped-pipe bugs that read fine as raw text. If you'd rather preview in the browser or the terminal, our guide to previewing markdown on Mac covers the alternatives.
When a Table Is the Wrong Choice
Tables win when readers scan across two or more dimensions — options against behavior, versions against changes, plans against features. They lose in a few predictable cases:
- Two columns where one side is long prose. Use a definition-style list: a bold term, then a paragraph. It reads better and it wraps on a phone.
- A single row. That's a sentence.
- More than about six columns. Nobody reads the right-hand side. Split it, or transpose it so the long axis is vertical.
- Anything that needs a code block per cell. Restructure as headings with code blocks underneath.
- Content that changes often and is maintained by hand. Every edit risks a broken row that renders silently.
A table's job is to make a comparison fast. When it stops doing that, the markdown is not the problem.
For the wider picture of what GitHub adds on top of standard markdown, see markdown on GitHub and our overview of GFM and MDX extensions. Tables also appear in Reddit markdown, but not in Slack or Discord messages — if the same content is going to a chat platform, plan on converting the table to a link.
Download OpenMark → — $9.99, one-time, native macOS. Format tables with one shortcut and see them rendered exactly as GitHub will show them.