The same Markdown renders three different ways on GitHub, npm, and a static-site generator. Most of the friction comes from a small set of syntax rules — trailing spaces, list indentation, fence languages — that the CommonMark spec nails down but every renderer interprets slightly differently. This markdown reference collects the subset that renders cleanly everywhere I have shipped docs: GitHub READMEs, npm packages, Astro and Hugo sites, and VS Code preview. Paste the examples, change the words, ship.
Headings, paragraphs, and line breaks
Headings use # followed by a space. One # is the document title, six is the smallest. CommonMark requires the space; ##Heading without it is plain text on most renderers.
# Project Title
## Installation
### Prerequisites
A blank line separates paragraphs. Without it, two consecutive lines become one paragraph. To force a soft line break inside a paragraph, end the line with two spaces or a backslash — the two-space form is invisible in most editors, so turn on whitespace visibility when you are debugging odd wrapping.
First line. <- two trailing spaces
Second line renders directly below it.
Emphasis, strong, and strikethrough
Single asterisks or underscores produce italic; double produce bold. Triple gives bold-italic. GitHub Flavored Markdown adds strikethrough with one or two tildes. Underscores in the middle of a word (my_variable) are not treated as emphasis by CommonMark, which is why code-like identifiers survive in prose.
*italic* _italic_
**bold** __bold__
***bold-italic***
~~struck through~~
Pick asterisks and stay with them. Mixing * and _ in the same document is a style smell that flags sloppy editing. To show a literal asterisk, backslash-escape it (\*) or wrap it in backticks (*).
Lists: ordered, unordered, nested, and task lists
Unordered lists use -, +, or *. Pick one and stick to it across the file; mixing them is legal but ugly in source. Ordered lists use a leading number and a dot, and the actual number does not matter — GitHub will renumber starting from the first value.
- First item
- Second item
- Nested item (two-space indent)
- Another nested item
- Third item
1. First step
1. Second step
1. Third step
Nested lists need consistent indentation. Two spaces per level is the GitHub convention; four also works, but mixing the two breaks the parser and flattens your tree. To put a paragraph or a code block inside a list item, indent the continuation to align with the list marker text — four spaces for a - item, five for 1. — or CommonMark will pull the text out of the list entirely. This is the single most common reason a README suddenly has a code block floating between two sections where it does not belong.
Task lists are a GitHub Flavored Markdown extension — checkbox items inside issues, PRs, and READMEs.
- [ ] Draft the API spec
- [ ] Implement the endpoint
- [x] Write integration tests
Code: inline and fenced blocks
Inline code uses single backticks. Fenced blocks use three backticks (or four-space indentation, which I avoid because it does not support language hints). The word immediately after the opening fence is the language identifier; GitHub uses it for syntax highlighting and to populate the copy button.
```python
def add(a, b):
return a + b
```
For longer blocks, some static-site generators (Docusaurus, Astro, MkDocs) render a filename from text after the language, like js title=auth.js. GitHub itself ignores that — it uses only the first word for syntax highlighting and silently drops the rest.
```js title=auth.js
export function sign(payload, key) {
return crypto.sign(payload, key);
}
```
When you need to show a fenced code block as raw Markdown, wrap it in a four-backtick fence. That is what I am doing throughout this article. Two extra backticks for each level of nesting — fiddly until you internalize it.
Links and images
Links are [text](url). Images are the same with a leading !. Reference-style links ([text][ref] plus [ref]: url at the bottom) keep prose readable when the URL is long; I use them mostly in long-form posts, not READMEs.
[CommonMark spec](https://commonmark.org/)

Relative links in a README resolve against the repo root on GitHub. Link to ./CONTRIBUTING.md, not /CONTRIBUTING.md, or the link will 404 when rendered elsewhere. GitHub autolinks bare URLs and issue references like #42 and @user in most contexts, so you usually do not need to wrap them. For mailto links, write the full form: [report a bug](mailto:[email protected]).
Anchors are generated from heading text: lowercased, spaces to hyphens, punctuation stripped. ## Installation Guide becomes #installation-guide. To link from elsewhere in the same document, use [see setup](#setup).
Tables
The pipe syntax. Pipes separate columns, hyphens mark the header row, and colons control alignment. The pipes on the outside edges are optional; I always include them because it makes diffs cleaner when you add a column. Column width in source does not affect rendering — line up the pipes for your own sanity when editing, and let a formatter like Prettier enforce it.
| Method | Path | Auth |
| :----- | --------------- | :--: |
| GET | `/api/users` | yes |
| POST | `/api/users` | yes |
| GET | `/api/health` | no |
:--- left-aligns, ---: right-aligns, :---: centers. Tables do not wrap cells or support block elements — keep them short. If a row gets unwieldy, switch to a definition list or a sub-section.
Blockquotes and horizontal rules
Blockquotes use >. They nest with >> and can contain most other Markdown, including lists and code. Horizontal rules are three or more hyphens, asterisks, or underscores alone on a line; --- is the convention I see most.
> Quoted text from the upstream spec.
> Continues on a second line.
>> Nested quote.
---
A new section below the rule.
One warning: a line of --- directly under a line of text is read as a setext heading (a level-2 heading underlined by the text above), not a horizontal rule. Put a blank line between them.
A related gotcha: GitHub strips the first level-1 heading from package READMEs because npm pulls the title from package.json instead. If your README opens with # My Package and the rendered page shows no title, that is why. Use level-2 headings for everything else and let the registry handle the title.
Escaping and special characters
CommonMark lets you backslash-escape any ASCII punctuation character. The ones worth knowing: \ “ * _ { } [ ] ( ) # + - . ! | ~ >. You rarely need them — wrapping the token in backticks is clearer and survives more renderers — but they are the answer when you need literal syntax inside running prose without breaking out of the paragraph.
Use \* for a literal asterisk, not a formatted one.
Escape the pipe in a table cell: `\|`.
HTML entities work too: —, ©, &. Most static-site generators pass them through unchanged, and GitHub renders the common ones. Prefer ASCII (---, (c)) in source for readability and reserve entities for things ASCII cannot express.
A note on rendering differences
GitHub Flavored Markdown is a strict superset of CommonMark with tables, task lists, strikethrough, and autolinks bolted on. npm strips most of it for package READMEs. Most static-site generators run CommonMark plus a hand-picked list of extensions, so a README that looks great on GitHub can render half-broken in your docs site. Before committing to a feature like footnotes or ==highlight==, check both targets. When in doubt, the safe subset is what this reference covers.
Footnotes are a common offender. The syntax [^1] in prose with [^1]: explanation at the bottom is supported on GitHub, but not on npm and not by default in many CommonMark implementations. If footnotes matter to you, pick a docs toolchain that documents its footnote support and use it only in long-form content, not in the README.
A practical workflow: keep your README to the safe subset — headings, lists, fenced code, links, images, tables, blockquotes. Move anything fancier into dedicated docs pages under docs/ where you control the renderer. The README is a landing page and a signpost; let it be simple.
FAQ
What is the difference between CommonMark and GitHub Flavored Markdown?
CommonMark is the standardized core syntax — headings, lists, code blocks, emphasis, links. GitHub Flavored Markdown extends it with tables, task lists, autolinks, strikethrough, and a few other features. Anything valid in CommonMark is valid in GFM; the reverse is not always true.
Why do my line breaks not show up?
You are probably missing the blank line between paragraphs, or the two trailing spaces needed for a soft line break. A single newline inside a paragraph is treated as a space by CommonMark, which is why copy-pasted text often collapses into one block.
How do I show raw Markdown syntax inside a Markdown file?
Use a fenced code block. To show a fenced code block itself, wrap it in a longer fence — four backticks around a three-backtick block, five around four, and so on. That is how every example in this reference is displayed.
Stop guessing, start rendering
Pull up the Markdown Editor and paste any block from this reference to see exactly how it renders before you commit it to a README. If you are writing prose and want to keep paragraphs tight, the Word Counter tracks length as you go.