The short version
- Tables, task lists, strikethrough and footnotes are GitHub extensions, not core Markdown. A renderer without them shows your table as pipe characters, and that is correct behaviour.
- A single newline inside a paragraph is a space, not a line break. Two trailing spaces or a backslash forces one.
- Nested lists need the child indented to line up with the parent's text, which for `1. ` is three spaces, not two.
- Embedded HTML is stripped or escaped by many renderers on purpose, because Markdown from an untrusted source is untrusted markup.
- Headings, lists, tables and fences almost always need a blank line before them. A block glued to the paragraph above it is read as part of that paragraph.
- Heading anchor ids are not standardised, so a contents list written for one site can break on another.
Markdown looks like a format with one definition and it is not. The original 2004 description was a Perl script and a page of prose, and it left enough unstated that every implementation had to invent answers. CommonMark arrived in 2014 to pin those answers down, and GitHub then defined its own superset on top of it. The result is that a document is only meaningful together with the thing rendering it.
That is the honest explanation behind almost every "my Markdown isn't working" question. The syntax is usually correct. It is correct for a renderer other than the one currently reading it — or it is one of a small number of genuine gotchas that trip everybody once, of which the disappearing line break is the most common by a wide margin.
This guide covers the failures worth knowing, in rough order of how often they bite: the flavour question, then tables, line breaks, nested lists, embedded HTML, blank lines, escaping, heading anchors and runaway code fences. Each one names what to look at rather than what to try.
First question: which flavour is rendering it?
Before debugging syntax, find out what is reading the file. Four things are in common use and they disagree in ways that account for most reported problems.
Original Markdown is the 2004 definition. Very little runs it today, but its ambiguities are why the others exist. CommonMark is the strict, unambiguous specification that most modern libraries implement as their base, and it deliberately contains no tables, task lists, strikethrough or footnotes. GitHub Flavored Markdown is CommonMark plus those extensions, and it is what people mean when they say "Markdown" without qualifying it. Then there are the chat and issue-tracker dialects — Slack, Discord, Teams — which are not Markdown at all in any formal sense; they borrow the punctuation and define their own rules.
The practical consequence: a README that renders correctly on GitHub can lose its tables on npm, in a documentation generator, or in an editor preview, without a single character changing. That is not a bug in your file. It is a feature the renderer does not implement.
| Feature | CommonMark | GitHub (GFM) | Original Markdown |
|---|---|---|---|
| Headings, lists, links, emphasis | Yes | Yes | Yes |
| Fenced code blocks | Yes | Yes | No — indent four spaces |
| Tables | No | Yes | No |
| Task lists | No | Yes | No |
| Strikethrough | No | Yes | No |
| Footnotes | No | Yes | No |
| Bare URLs become links | No | Yes | No |
| Raw HTML passed through | Yes | Yes, filtered | Yes |
Tables that render as pipe characters
Two causes, and they are easy to tell apart. If every pipe in the table shows as a literal `|`, the renderer does not support tables — they are a GFM extension. If some of the table renders and some does not, the delimiter row is wrong.
The delimiter row is the line of dashes under the header, and it is what turns pipes into a table. It must have the same number of columns as the header row, and each cell must contain at least one dash. Colons set alignment: `:---` left, `---:` right, `:---:` centred. Miss the row entirely and you have a paragraph containing pipes, which is exactly what a renderer will show you.
The outer pipes at the start and end of each line are optional, and the columns do not need to line up in the source — the widths in your editor have no effect on the output. What does matter is that a cell containing a literal pipe must escape it as `\|`, or the row gains a column and stops matching the header.
One thing no flavour of Markdown supports: a table cell containing a block. No paragraphs, no lists, no fenced code inside a cell. Inline code and `<br>` are the usual workarounds.
- Every pipe literal → the renderer has no table support. Check the flavour.
- Header row and delimiter row must have the same column count.
- A literal pipe inside a cell must be written `\|`.
- Alignment colons go in the delimiter row, not the header.
- No blank line between the header and the delimiter row.
Line breaks that disappear
This is the single most common Markdown surprise, and it is deliberate. Inside a paragraph, a newline in the source is treated as a space. Two lines of an address written on separate lines come out as one run-on line, because Markdown reflows paragraphs the way HTML does.
There are three ways to get an actual break. End the line with two or more spaces — the original syntax, and invisible in every editor, which is why it is also the most fragile. End it with a backslash, which CommonMark added precisely because the trailing-space rule cannot be seen. Or write a real `<br>`.
The fourth way is not yours to choose: many renderers turn on a `breaks` option that makes every single newline a `<br>`. GitHub does this in issues, pull requests and comments — but not in `.md` files in a repository. That difference is why the same text behaves one way in an issue and another in the README, and why people conclude Markdown is inconsistent when it is in fact two different configurations of the same renderer.
For a blank line between paragraphs, leave a genuinely blank line. A line containing only spaces counts as blank; a line containing a non-breaking space does not, and that is a real thing that happens when text is pasted from a word processor.
Nested lists that flatten or break the list
A nested list item has to be indented far enough to line up with the *content* of its parent item, not with the parent's marker. For `- item` the content starts at column 3, so two spaces is enough. For `1. item` the content starts at column 4, so a nested item needs three spaces — and two spaces, which looks right, produces a flat list.
The safe habit is to indent nested items by four spaces regardless of marker. That satisfies every renderer, and it is what most style guides recommend for exactly this reason.
The other list failure is a numbered list that restarts at 1, or renders as separate lists. That is caused by a blank line plus non-indented content between items, which ends the list; the next number starts a new one. And a list interrupted by a paragraph at the left margin will always split — continuation content inside a list item must be indented to the item's content column too.
Numbering itself is not something you control by writing different numbers. `1.` `1.` `1.` renders as 1, 2, 3 in every implementation. Only the first number matters, and it sets the start.
| Parent marker | Content starts at | Indent the child by |
|---|---|---|
| `- ` or `* ` or `+ ` | Column 3 | 2 spaces (4 is safer) |
| `1. ` through `9. ` | Column 4 | 3 spaces (4 is safer) |
| `10. ` and up | Column 5 | 4 spaces |
HTML that is stripped, escaped or ignored
CommonMark passes raw HTML straight through, which means a Markdown document is a document that can emit arbitrary markup. That is exactly why so many renderers do not pass it through: a `.md` from a repository, a comment box or a user is untrusted markup, and rendering it unfiltered is a cross-site scripting hole.
So there are three behaviours you might be seeing. The HTML renders — the renderer allows it, possibly through an allowlist that removes scripts, event handlers, iframes and inline styles. The HTML appears as visible text — the renderer escaped it, which is the safe default in many static site generators. Or the HTML disappears entirely — the renderer stripped it.
Even where HTML is allowed, one rule catches people out: Markdown inside a block-level HTML element is usually not processed. Writing `<div>` then `**bold**` then `</div>` gives you literal asterisks in most renderers, because the whole thing is one HTML block. GitHub's fix is a blank line — leave one after the opening tag and before the closing tag, and the Markdown between them is parsed normally.
If you are checking whether specific markup survives a filter, render the document somewhere that tells you what it removes rather than guessing from what is missing.
- `<script>`, `<iframe>`, `<style>`, `<form>` and every `on*` attribute are removed by any renderer worth using.
- `style` and `class` attributes are usually removed too, which is why inline colours rarely survive.
- A blank line after `<div>` and before `</div>` is what lets the Markdown inside be parsed.
- `<details>` and `<summary>` are widely allowed, and are the standard way to make a collapsible section.
- `<img align="right">` works where HTML is allowed and is the usual way to float a logo in a README.
Blocks that need a blank line before them
A heading, list, table, blockquote or fenced code block written directly under a line of text is, in strict CommonMark, part of that paragraph. This is called lazy continuation, and it is why a heading sometimes renders as body text with a `#` in front of it.
The exception is ATX headings, which CommonMark does allow to interrupt a paragraph — but tables and fenced blocks generally do not, and original Markdown did not allow headings to either. Since a blank line always works and costs nothing, the rule to internalise is simply: leave a blank line before any block that is not a paragraph.
The same applies after. A list followed immediately by a paragraph at the left margin can absorb that paragraph as part of the final item, or terminate awkwardly. A blank line settles it.
Characters that vanish, and how to escape them
Underscores in `snake_case_names` are the usual complaint, and modern renderers already handle it: CommonMark's rules say an underscore inside a word cannot open or close emphasis, precisely so identifiers survive. Asterisks have no such protection, because `*` is used deliberately mid-word — so `a*b*c` does produce emphasis where `a_b_c` does not.
Anything that would be read as syntax can be escaped with a backslash: `\*`, `\_`, `\#`, `\[`, `\`` and so on. Backslash escaping works only on punctuation; `\a` renders as a literal backslash followed by an a.
For anything code-shaped, backticks are better than escaping. Text inside a code span is taken literally, so `` `*args` `` and `` `<div>` `` need nothing else. When the content itself contains a backtick, use more backticks to delimit it: two on the outside, and a space either side if the content starts or ends with one.
Angle brackets are the other trap. `<something>` is read as an HTML tag or an autolink, so a placeholder like `<your-api-key>` can silently disappear. Wrap it in backticks or escape the opening bracket as `<`.
Heading anchors that only work in one place
Markdown has no specification for heading ids. Every renderer that generates them invented its own rule, so `## Set up the API` might become `#set-up-the-api` in one place and `#setuptheapi` or `#header-set-up-the-api` in another. A table of contents written against one renderer can be entirely dead links in another.
GitHub's rule is the closest thing to a de facto standard: lowercase the text, remove anything that is not a letter, number, space, hyphen or underscore, then replace spaces with hyphens. Repeated headings get `-1`, `-2` and so on appended in document order. Emoji and punctuation are dropped, which is why `## 🚀 Getting Started` is `#-getting-started` — with a leading hyphen, from the space the emoji left behind.
If a link must work everywhere, write an explicit anchor rather than relying on a generated one: an `<a id="install"></a>` immediately above the heading, where HTML is permitted. Otherwise, generate the contents list with the same renderer that will display it.
Code fences that swallow the rest of the document
If everything after a certain point renders as one large code block, a fence was opened and never closed. The closing fence must be at least as long as the opening one and must be on a line of its own — a fence with trailing text is not a closing fence.
The subtler version happens inside lists. A fence inside a list item has to be indented to the item's content column, and the closing fence has to be indented the same amount. Mismatch them and the block either does not start or does not end.
Nesting a fenced block inside another one requires the outer fence to be longer: four backticks around a block that itself contains three. This is how you document Markdown in Markdown, and it is the one case where the fence length is doing real work rather than being decoration.
Finally, a backtick fence's info string cannot contain a backtick. `` ```js `example` `` opens a fence whose language is nonsense, and the parse goes sideways from there. Tilde fences (`~~~`) have no such restriction and are the escape hatch.
Try it on Markdown Viewer
Free, no signup, no watermark. Runs on Zyff’s servers, so you get the same result on a phone as on a desktop.
How to work out why a Markdown document isn't rendering
Identify the renderer
Name the thing displaying the file: GitHub, npm, a static site generator, an editor preview, a chat app. Half of all Markdown problems are a feature the renderer does not have, and no amount of editing the file will fix that.
Test the same text under both flavours
Render the document once with GitHub Flavored Markdown on and once with strict CommonMark. If tables, task lists, strikethrough or footnotes break in the second, you have found the answer: those are GFM extensions, and the renderer that is failing does not implement them.
Check the blank lines around the broken block
Put a blank line before and after any heading, list, table, blockquote or fenced code block. A block glued to the paragraph above it is read as part of that paragraph, and this alone fixes a large share of reported problems.
Check indentation on anything nested
Nested list items must line up with the parent item's text, not its marker — three spaces under `1. `, two under `- `. Four spaces always works. A fence inside a list item needs the same indentation on both of its fences.
Escape or fence the characters being eaten
Wrap anything code-shaped in backticks, and escape stray `*`, `_`, `#` or `[` with a backslash. A placeholder in angle brackets is being read as a tag; put it in backticks.
Render the fixed file before you commit it
Paste it into a viewer and read it. Markdown problems are visual, and reasoning about them from the source is how a second one gets introduced while fixing the first.
At a glance
| Line break inside a paragraph | Two trailing spaces, a trailing backslash, or a literal <br> |
|---|---|
| Nested item under a bullet | 2 spaces minimum, 4 for safety |
| Nested item under `1. ` | 3 spaces minimum, 4 for safety |
| Table delimiter row | Required; must match the header's column count |
| Literal pipe in a cell | Escape it as \| |
| Nesting a fence in a fence | Outer fence must be longer — 4 backticks around 3 |
| GitHub anchor slug | Lowercase, punctuation removed, spaces to hyphens, repeats get -1 |
| Backslash escaping | Works on punctuation only |
Frequently asked questions
Why is my Markdown table not rendering?
Either the renderer does not support tables, or the delimiter row is wrong. Tables are a GitHub Flavored Markdown extension rather than part of core Markdown, so a strict CommonMark renderer shows every pipe as a literal character — that is correct behaviour, not a bug. If only part of the table breaks, check that the row of dashes under the header has exactly as many columns as the header itself.
Why did my line breaks disappear in Markdown?
Because a single newline inside a paragraph is treated as a space, not a break. End the line with two spaces or a backslash to force one, or use a literal <br>. GitHub applies an option in issues and comments that turns every newline into a break, but does not apply it to .md files in a repository, which is why the same text behaves differently in the two places.
Is GitHub Markdown different from normal Markdown?
Yes. GitHub Flavored Markdown is CommonMark plus tables, task lists, strikethrough, footnotes, autolinked bare URLs and alert callouts. Everything in core Markdown works on GitHub; the reverse is not true, which is the source of most "it worked on GitHub" reports.
Why is my HTML not working in Markdown?
Most likely the renderer strips or escapes it deliberately. Markdown passes raw HTML through by design, which makes an untrusted document a source of arbitrary markup, so many renderers filter it through an allowlist or escape it entirely. Where HTML is allowed, remember that Markdown inside a block-level element is only parsed if you leave a blank line after the opening tag.
Why does my nested list render flat?
The child item is not indented far enough. It has to align with the parent's text rather than its marker, so a nested item under `1. ` needs three spaces and two is not enough — even though two looks correct. Indenting every nested level by four spaces satisfies every renderer.
Why do my heading links work on GitHub but not on my site?
Because heading ids are not standardised and each renderer generates them differently. GitHub lowercases the text, strips punctuation and replaces spaces with hyphens; another renderer may keep punctuation or add a prefix. Either generate the contents list with the renderer that will display it, or place an explicit anchor element above each heading where HTML is allowed.
How do I show a literal asterisk or underscore?
Escape it with a backslash — `\*` and `\_` — or wrap the text in backticks, which makes everything inside literal. Underscores inside a word are already safe in CommonMark because an intraword underscore cannot open emphasis, which is what keeps snake_case identifiers intact; asterisks have no such exemption.