Why Your Markdown Table Is Not Rendering
You wrote a Markdown table, pushed it to GitHub, and instead of clean rows and columns you got a wall of pipes and dashes printed as plain text. The frustrating part is that Markdown tables look forgiving but are not: a single missing character flips the whole block from "table" to "paragraph." The good news is that almost every broken table comes down to one of five causes, and each has a deterministic fix. Here they are, worst offenders first, with a broken version and the corrected version for each.
1. There is no blank line before the table
This is the most common cause and the most renderer-dependent. Many parsers treat a table only as a real table when it begins on a block boundary — meaning the line above it is blank, or the table is the first thing in the document. If a line of prose sits directly above the table with no empty line between them, those parsers fold the table into the preceding paragraph and render it as text.
The kramdown parser, which is the default engine GitHub Pages uses with Jekyll, is explicit about this: tables "have to start and end on block boundaries" and so cannot be embedded inside a paragraph. GitHub.com's own GitHub Flavored Markdown (GFM) is more lenient and does not require the blank line, which is exactly why a table can look perfect in a GitHub issue and then break on the published GitHub Pages site.
Broken (table glued to the paragraph above):
Here are the results:
| Name | Score |
| --- | --- |
| Ada | 99 |
Fixed (blank line inserted):
Here are the results:
| Name | Score |
| --- | --- |
| Ada | 99 |
Because this fix is harmless everywhere and required by stricter parsers, always leave a blank line above and below your tables.
2. The separator row is malformed
The second line of a table — the one with the dashes — is the delimiter (or separator) row, and it is what tells the parser "this is a table, not text." It must contain only hyphens, optional colons for alignment, pipes, and spaces. If it contains anything else, or if a cell in it has no hyphen at all, the parser gives up.
A widely repeated claim is that you need at least three dashes per cell. That is a convention, not a universal rule: the GFM specification does not mandate a minimum dash count, and kramdown only requires "at least one dash and one pipe character" in the separator line. So a single hyphen per cell is valid in those engines. The real failures are a separator cell with zero hyphens, or a stricter linter or renderer that enforces the three-dash style. Using three dashes per cell is the safe default because it satisfies every common parser and most linters.
Broken (a separator cell with no hyphen):
| Name | Score |
| | ----- |
| Ada | 99 |
Fixed (every cell has hyphens):
| Name | Score |
| ---- | ----- |
| Ada | 99 |
To set alignment, add a colon: :--- for left, ---: for right, and :---: for centered.
3. The column count does not match across rows
The GFM specification is blunt about this: "The header row must match the delimiter row in the number of cells. If not, a table will not be recognized." So if your header has three cells and your separator has two, the entire block renders as raw text. This is easy to introduce by hand when you add a column to the header but forget the separator, or the other way around.
Body rows are treated more gently. Per the spec, a body row with fewer cells than the header gets empty cells inserted, and a row with more cells has the excess ignored. So a ragged data row will usually still render — it is the header-versus-separator mismatch that kills the table entirely.
Broken (header has three cells, separator has two):
| Name | Score | Rank |
| ---- | ----- |
| Ada | 99 | 1 |
Fixed (counts match):
| Name | Score | Rank |
| ---- | ----- | ---- |
| Ada | 99 | 1 |
4. You are looking at a different renderer than you think
"It works on GitHub but not on my docs site" is a renderer mismatch, not a bug in your Markdown. GitHub.com renders comments and READMEs with GFM. GitHub Pages, by default, renders with Jekyll and the kramdown engine, which has stricter table rules (see cause 1). Static site generators, documentation tools, and chat apps each ship their own parser, and some have tables turned off entirely unless an extension is enabled.
If your tables work in a GitHub issue but break on a Jekyll-built GitHub Pages site, the standard fix is to tell Jekyll to use GFM-style parsing. In your _config.yml:
markdown: kramdown
kramdown:
input: GFM
Setting kramdown's input mode to GFM relaxes the parser toward GitHub's behavior. If you are choosing or comparing engines, our guide to Markdown flavors explains where GFM, CommonMark, and kramdown diverge so you can predict these differences instead of discovering them in production.
5. An unescaped pipe inside a cell breaks the layout
The pipe character is the column separator, so a literal pipe inside cell text is read as the start of a new column. That throws off the cell count for that row and can mangle the table. The fix is to escape the literal pipe with a backslash: \|. The GFM spec tells you to "include a pipe in a cell's content by escaping it," and kramdown agrees that "literal pipe characters need to be escaped." Both make an exception for pipes inside code spans, which are taken literally.
Broken (the regex contains a bare pipe):
| Pattern | Matches |
| ------- | -------- |
| a|b | a or b |
Fixed (pipe escaped):
| Pattern | Matches |
| ------- | -------- |
| a\|b | a or b |
Stop hand-aligning pipes
Hand-editing tables is where all five of these bugs come from. Padding cells to line up, counting dashes, and tracking column counts by eye is exactly the kind of work a tool should do for you. Instead of fighting the syntax, generate a guaranteed-valid table with our Markdown table builder, which keeps the header, separator, and body cell counts in sync automatically and lets you set alignment without typing colons. Before you commit, paste it into the Markdown preview to confirm it renders the way you expect. And if you already have an HTML table you want to bring into a README, the HTML to Markdown converter produces the table syntax for you so there are no pipes to align by hand at all.
When a table refuses to render, walk the list in order: blank line above it, a hyphen in every separator cell, matching header and separator column counts, the right renderer, and escaped pipes. One of those five is almost always the culprit.
Frequently Asked Questions
GitHub.com uses GitHub Flavored Markdown, which is lenient. GitHub Pages defaults to Jekyll with the kramdown engine, which is stricter and requires a blank line before the table. Add the blank line, and in your _config.yml set kramdown's input mode to GFM to align the two parsers.
No. The GFM spec sets no minimum, and kramdown needs only one dash and one pipe in the separator line, so a single hyphen per cell is valid in those engines. The real failures are a separator cell with zero hyphens or a linter that enforces three. Using three dashes is the safe convention because it satisfies every common parser.
Escape it with a backslash: write \| instead of |. Because the pipe is the column separator, an unescaped one starts a new column and breaks the row. Pipes inside inline code spans are taken literally and do not need escaping in GFM or kramdown.
The parser failed to recognize the block as a table. The usual causes are a missing blank line above it, a malformed separator row, or a header whose cell count does not match the separator row. Per the GFM spec, if the header and delimiter rows differ in cell count, no table is recognized at all.
The header and separator rows must have the same number of cells, or the table is not recognized. Body rows are more forgiving: rows with too few cells get empty cells added, and rows with too many have the extras ignored. So mismatched body rows usually still render, but a header-separator mismatch breaks everything.