How Markdown actually works

In short
  • Markdown is a handful of punctuation marks that a converter turns into real HTML.
  • This page covers every core element: headings, emphasis, lists, code, links, images, rules, escaping and inline HTML.
  • Each part shows the characters you type, the output you get, and the mistake that breaks it.
  • Read it once end to end, then treat it as a lookup. Paste the examples into the live editor as you go.

Markdown is not a program, and it is barely a file format. It is a convention. A few punctuation marks describe structure inside ordinary plain text.

You type # before a title. You wrap a phrase in asterisks. You start lines with hyphens. A converter then turns those marks into real HTML. That converter is called a parser or a renderer, and it emits tags like <h1>, <strong> and <ul>.

The clever part of the design is that the source stays readable. Open a Markdown file in any text editor and it still looks like a sensible document, marks and all.

That is why it became the standard for READMEs, wikis, note apps, static-site blogs and chat platforms. It is also why a file you write today will still open in thirty years. For the origin story and the wider case for the format, read What is Markdown?. If you are torn between raw HTML and Markdown, our Markdown vs HTML comparison settles it.

One caveat before we start. Markdown has dialects. The 2004 spec left gaps, and each ecosystem filled them its own way long before the CommonMark specification pinned the core down in 2014.

The dialect that matters most today is GitHub Flavored Markdown (GFM). It adds tables, task lists and strikethrough. Everything below works in GFM and in nearly every modern renderer, including our own online editor. Each element also has a deeper page of its own, gathered on the Markdown syntax hub.

Illustration of plain text with markdown symbols flowing through a converter and emerging as a beautifully formatted web page

Headings

The six heading levels

Put one to six hash marks (#) at the start of a line. That gives you a heading of level one to six. Always leave a space between the hashes and the text, because some renderers refuse the heading without it.

# Heading level 1
## Heading level 2
### Heading level 3
#### Heading level 4
##### Heading level 5
###### Heading level 6

Those six lines become HTML <h1> through <h6>. Here is what each level is for:

  • # gives <h1>, the document title. Use it once.
  • ## gives <h2>, a major section of the page.
  • ### gives <h3>, a sub-topic inside a section.
  • #### gives <h4>, useful in long technical docs.
  • ##### and ###### give <h5> and <h6>. You will rarely need them.

Most parsers also accept a matching run of hashes at the end of the line, as in ## Heading level 2 ##. Those closing hashes are decoration. They never show up in the output.

Alternate Setext syntax

The top two levels have an older alternate syntax called Setext. You underline the text with equals signs or hyphens.

Heading level 1
===============

Heading level 2
---------------

The number of underline characters makes no difference. One = behaves exactly like twenty.

Watch out: the hyphen version collides with horizontal rules. A stray line of hyphens under a paragraph turns that paragraph into a level-2 heading instead of drawing a divider.

Heading best practices

Four habits keep a heading hierarchy clean and portable:

  • Use one level-1 heading per document. It is the title, so there is only ever one.
  • Never skip a level. An h4 directly under an h2 confuses readers and screen readers alike.
  • Leave a blank line above and below every heading. A few renderers misparse a heading glued to the text around it.
  • Prefer the # style over underlines. It covers all six levels, and the level is obvious at a glance.

There is one more reason to keep the hierarchy tidy. Most renderers build an anchor id out of each heading, so ## Line breaks becomes a linkable #line-breaks. Clear, unique heading text hands you clean deep links into your own document for free. Our headings reference goes further into anchors, casing and outline structure.

Paragraphs and line breaks

Starting a new paragraph

Paragraphs need no syntax at all. Any run of consecutive text lines is one paragraph, and a blank line starts a new one.

This is the first paragraph.

This is the second. The blank line above is what separates them.

A line holding nothing but spaces or tabs counts as blank. Stacking several blank lines changes nothing, since two paragraphs is the most any gap can produce. If you want visible vertical space, use a horizontal rule or a heading instead.

Forcing a line break

The single newline is what surprises beginners. Pressing Enter once does not break the line in the output. Markdown joins those lines into one flowing paragraph.

To force a line break within a paragraph, for an address or a poem, end the line with two or more spaces:

Roses are red
Violets are blue

Tip: those two spaces sit at the end of the line, where nothing on screen shows them. If the break refuses to appear, put the cursor at the end of the line and tap the right arrow key. Two extra presses before the cursor jumps down means the spaces are really there.

Trailing spaces vs the br tag

Trailing spaces are invisible, and plenty of editors, linters and commit hooks strip them without asking. The sturdier option is the HTML tag <br> at the end of the line. Every Markdown renderer accepts it.

Some dialects treat every single newline as a real break, including the ones behind most chat apps. That is convenient at home. Do not rely on it for text that will travel between platforms. Paste both forms into the live editor and you will see the difference at once.

Watch out: never indent a paragraph with spaces or tabs. Four leading spaces mean “code block” in Markdown, and that is the classic cause of text that turns monospaced for no obvious reason.

Emphasis: bold, italic and strikethrough

Bold

Wrap text in two asterisks or two underscores to make it bold. That becomes an HTML <strong> element.

**bold**      or  __bold__

The two markers are interchangeable, but asterisks are the safer habit. Underscores fail in the middle of words, so snake_case_names stays literal in most renderers by design. Asterisks still work mid-word, so fan**tas**tic renders as fantastic.

Italic

One marker instead of two gives you italic, an HTML <em> element.

*italic*      or  _italic_

The same word-boundary rule applies, so * is again the portable choice. Pick one marker for the whole document and stay with it. Mixing them is legal, but it makes the source harder to scan and it defeats most style checkers. Our bold and italic reference works through the awkward cases around punctuation.

Bold and italic together

Three markers combine both. ***bold italic*** renders as bold italic, nesting an <em> inside a <strong>.

***bold italic***
**_also bold italic_**

The second line mixes the two markers on purpose. That form is the more reliable one when the emphasis starts or ends next to punctuation, because the parser can tell which pair closes which.

Strikethrough

Strikethrough is a GFM extension, not original Markdown. Wrap the text in double tildes:

~~crossed out~~

That renders as crossed out. It works on GitHub, GitLab, Discord, Reddit and in our editor, but not in strict old-school parsers.

Two mistakes break every marker in this section:

  • Spaces just inside the markers. ** bold ** will not render at all.
  • An unclosed pair. The asterisks simply stay on screen as literal text.

If you ever blank on which symbol does what, the printable syntax sheet has the full quick-reference table.

Blockquotes

A basic blockquote

Start a line with a greater-than sign to quote it:

> The best way to predict the future is to invent it.

which renders as:

The best way to predict the future is to invent it.

For a quote of several paragraphs, put a > on the blank line between them too. Most parsers accept a lazy form where only the first line of each paragraph carries the marker. The fully marked version is the one every renderer agrees on.

Tip: leave a blank line before and after every blockquote. Without it, some renderers pull the neighbouring paragraph into the quote and give you no warning at all.

Nested blockquotes

Blockquotes nest. Add a second > for a quote inside a quote, and a third for one level deeper. This is how email reply chains and threaded discussions have always been shown in plain text.

> The original message.
>
> > And the reply to it.

Other elements inside a blockquote

A blockquote can hold any other Markdown element: headings, emphasis, lists, even code. Keep the > on every line, including the blank ones that separate the blocks.

> #### A quoted heading
>
> First quoted paragraph, with **bold** text.
>
> > A nested quote inside the quote.
>
> - a list item inside the quote
> - another item

Blockquotes are the natural tool for quoting email, citing sources and flagging callouts in documentation. GitHub builds on that with alert syntax. Begin the quote with a line such as > [!NOTE] or > [!WARNING] and GitHub paints a coloured callout, while everywhere else it falls back to an ordinary quote. Our blockquotes guide lists every alert type and how each one degrades.

Lists: ordered, unordered, nested and task lists

Unordered lists

Unordered lists start each line with a marker. Three are legal:

  • - a hyphen. This is the common convention and the one most linters expect.
  • * an asterisk. Popular with writers who like the symmetry with emphasis.
  • + a plus sign. Legal, but rare enough to look like a typo.
- first item
- second item
- third item

Tip: choose - and use it everywhere. Switching marker halfway does not continue the same list. Most parsers close the first list and open a second one, which is a surprisingly common cause of unexplained gaps and restarted numbering.

Watch out: a list needs a blank line above it. Put a bullet directly under a paragraph and many renderers swallow it into that paragraph. The same rule applies to headings, quotes and code fences.

Ordered lists

Ordered lists start with a number and a period:

1. first step
2. second step
3. third step

Here is a useful quirk. The actual numbers do not matter, and only the first one sets the start. 1. then 1. then 1. still renders as 1, 2, 3.

Many writers number every item 1. for that reason, so reordering never means renumbering. Begin at 5. instead and a compliant renderer emits <ol start="5">. That is how you continue a list that a paragraph interrupted.

Nested lists

To nest a list inside a list, indent the child items. Four spaces, or one tab, is the amount that works everywhere. Two spaces works in many renderers but not all. You can mix ordered and unordered levels freely:

1. Prepare the release
    - bump the version number
    - update the changelog
2. Publish
    - tag the commit
    - push to the registry

To put a paragraph or code block inside a list item, indent it to line up with the item's text rather than with the bullet, and keep a blank line above it. Get that indentation wrong and the block escapes the list entirely. Our lists guide covers deep nesting and mixed lists in detail.

Task lists

GFM adds task lists, which show up as interactive checkboxes on GitHub:

- [x] write the guide
- [ ] proofread it
- [ ] publish

Two rules matter here. The brackets must follow a normal list marker, and the space inside an unchecked box is required.

In GitHub and GitLab issues and pull requests the boxes are clickable. Ticking one rewrites the underlying Markdown for everyone on the thread. Elsewhere they degrade gracefully into plain bullets with literal brackets.

Code: inline, fenced blocks and syntax highlighting

Inline code

Code is where Markdown earns its keep for technical writers, and the rule is simple. Anything marked as code renders literally, in a monospaced font, with all Markdown processing switched off inside it.

For a fragment inside a sentence, wrap it in single backticks: `npm install`. Variable names, commands and filenames all belong in that form.

If the fragment itself contains a backtick, wrap it in double backticks instead. Leave a space just inside each marker so the runs cannot merge. Inline code is also the honest way to show Markdown symbols in prose without escaping every one of them.

Fenced code blocks

For whole blocks, use a fence: a line of three backticks before and after the code.

```
plain, unhighlighted code
```

Quoting code that contains a line of three backticks? Open and close with four or more instead. A fence only ends on a run at least as long as the one that opened it. Tildes (~~~) work as fence characters too, which is the tidiest way to wrap one fenced block inside another.

Tip: close every fence you open. An unclosed fence swallows the whole rest of the document into one grey box, and no renderer will warn you. If a page goes monospaced from the middle down, scroll up to the last fence.

Syntax highlighting with language tags

Write a language name right after the opening fence, such as ```python, ```js or ```bash. Renderers that support highlighting will then colour the code. That covers GitHub, GitLab, most static-site generators and our editor.

```python
def greet(name):
    return f"Hello, {name}!"
```

The tags you will use most often are these:

  • python, js, ts, go, rust and other language names, spelled the way the highlighter expects.
  • bash or sh for shell commands and terminal sessions.
  • json, yaml, html, css, sql for data and markup.
  • diff to colour added and removed lines green and red.
  • text when the block is not code at all, such as log output.

Tip: add the tag every time. It costs nothing where highlighting is unsupported, and an unrecognised tag is ignored rather than breaking the block.

Several ecosystems reuse that slot for extras. Some site generators accept a filename or a line-highlight range after the language name. Our code blocks guide lists the identifiers each major platform recognises.

The four-space indent alternative

The original alternative still works. Indent every line of the block by four spaces and it becomes code, with no fence needed.

Fences have won in practice. They need no re-indenting when you paste code in, they carry the language tag, and you cannot trigger one by accident.

The four-space rule survives mainly as a trap. Indent a normal paragraph and you get an unexpected code block. If you write a lot of code-heavy docs, the one-page syntax reference keeps all three forms one glance away.

Illustration of a code block with colorful syntax highlighting sitting inside a fenced frame of backticks

Images

Image syntax

An image is a link with an exclamation mark in front. The square brackets hold the alt text, the parentheses hold the image URL, and an optional quoted title follows:

![A split-screen markdown editor](/img/md-editor-screenshot.png "MD Editor in action")

Reference-style definitions work for images too, which keeps long asset paths out of your prose. Remember that relative paths resolve against the rendered page, not the source file. A README image that looks fine on your disk can still break once the file is published. Our images guide digs into hosting, paths and sizing.

Why alt text matters

Never leave the alt text empty. It is what screen readers speak, what search engines index, and what shows if the image fails to load.

Describe what the image shows, in a phrase, as if you were telling someone over the phone. ![screenshot] is weak. ![Side-by-side editor with raw Markdown left and rendered preview right] does the job.

There is one legitimate exception. For a purely decorative image, deliberately empty alt text tells a screen reader to skip it instead of announcing a filename.

Linked images

To make an image clickable, nest the image syntax inside a link's square brackets:

[![MD Editor preview](/img/thumb.png)](https://mdeditor.tw/)

This is how the badge rows at the top of a README are built. Each badge is a small image wrapped in a link to the service that generates it. The alt text still matters here, because it is what a screen reader announces for the link.

Controlling size

Markdown itself cannot resize or align images. There is no width syntax. When you need width="400" or centring, drop down to an HTML <img> tag. The inline HTML section below covers the rules, and Markdown vs HTML goes deeper.

A few ecosystems invent their own shorthand, and GitHub accepts plain HTML plus query parameters on image URLs. None of it is portable. Assume the HTML tag is the only option that travels.

Horizontal rules

Three or more hyphens, asterisks or underscores alone on a line produce a horizontal rule, an <hr> divider:

---

***

___

All three render identically, so pick one and stay with it.

Watch out: --- directly under a line of text is the alternate level-2 heading syntax from the headings section. The text above turns into a heading and no rule appears. Surround every horizontal rule with blank lines and the ambiguity vanishes.

Use rules sparingly. One between major thematic sections is plenty. A heading is usually the better separator, because it also gives the new section a name.

Escaping characters with backslash

The backslash escape

Sometimes you want a literal asterisk, hash or bracket, with no Markdown interpretation at all. Put a backslash in front of the character to escape it:

\*not italic\* - Renders with visible asterisks

\# Not a heading

1\. Not a list item, just "1." starting a sentence.

A backslash in front of a character with no special meaning is usually left alone, so a regular expression's \d survives intact in most parsers. That behaviour is a convention rather than a guarantee. It is one more reason to put symbol-heavy text inside code markers.

Which characters can be escaped

Sixteen characters respond to a backslash:

  • \ backslash and ` backtick
  • * asterisk and _ underscore
  • { } curly braces
  • [ ] square brackets
  • ( ) parentheses
  • # hash, + plus and - hyphen
  • . period, ! exclamation mark and | pipe

In practice you reach for the backslash in four places: asterisks in ordinary prose, a hash at the start of a line, a number-period that would start an unwanted ordered list, and pipes inside table cells.

Two related notes. Inside code spans and code blocks nothing needs escaping, which is exactly what they are for, and that is usually the cleaner solution for anything syntax-heavy. To show a literal backslash, escape it with another backslash: \\.

For a character outside the list above, such as an angle bracket you want displayed rather than read as a tag, reach for the HTML entity &lt; instead.

Inline HTML: the escape hatch

When to reach for HTML

Markdown's author built in a deliberate escape hatch. Any HTML you type in a Markdown document passes through to the output untouched. That is the answer whenever Markdown has no syntax for what you need:

  • <u> for underlined text, which Markdown has no marker for.
  • <kbd> for keyboard keys in a shortcut list.
  • <sup> and <sub> for superscript and subscript.
  • <img width="400"> for an image at a set size.
  • <details> and <summary> for a collapsible section, which GitHub READMEs use constantly.

Treat HTML as seasoning, not the meal. Every tag makes the source less readable, and readable source is the very thing Markdown exists to protect.

If you find yourself writing more tags than text, you may genuinely want an HTML document instead. Markdown vs HTML walks through where that line sits. For most people the honest tally is two or three <br> and <details> tags per document, and everything else stays Markdown.

How to write it safely

The main rule of mixing is about blank lines. Separate block-level HTML such as <div>, <table> and <details> from the surrounding Markdown with blank lines.

Do not expect Markdown syntax to keep working inside those blocks either. Classic parsers switch Markdown off inside block-level tags. Inline tags like <kbd> or <sup> mix freely mid-sentence with no such ceremony.

Close every tag you open, even the ones HTML would forgive, and quote every attribute value. An unclosed <div> can swallow the rest of the page. The renderer treats the block as opaque, so it will never warn you about it.

What renderers strip

Some environments strip HTML entirely for security. Reddit, many chat apps and some static-site configs all do. Write the surrounding Markdown so it still reads sensibly once the tags vanish.

Others sanitize selectively. GitHub keeps <details>, <kbd> and sized images, but removes <script>, <style>, form controls and most inline style attributes.

The safe assumption is that anything beyond simple presentational markup gets discarded somewhere. Test the pages that matter in the renderer that will actually publish them. One preview is not proof, and Babelmark settles an argument fast by running the same input through dozens of parsers at once, while our Markdown file viewer is a quick way to check a whole file before you commit it.

Illustration of an HTML tag being slotted into a plain-text Markdown document like a puzzle piece

Best practices and common mistakes

Best practices

These are the positive habits that keep documents portable across renderers, and the syntax hub has a deeper page behind each one:

  • Blank lines around every block - Headings, lists, quotes, code fences, tables. It costs nothing and removes most parser disagreements.
  • Be consistent: one bullet marker (-), one emphasis marker (*), #-style headings, fenced code with language tags.
  • One h1, no skipped levels - Good for readers, screen readers and SEO alike.
  • Write real alt text on every image, not just a filename.
  • Preview before you publish. Paste your draft into the live editor and the preview updates on every keystroke.

Common mistakes

Almost every Markdown bug in the wild is one of a short list. Check these first:

#Heading            ← missing space after # - Won't render
** bold **          ← spaces inside markers - Won't render
1) item             ← use "1." not "1)" for portability
  - nested item     ← 2-space nesting fails in some renderers; use 4
text
- list item         ← no blank line before the list - May not render

When a document renders wrong, work through this in order:

  1. Look for a missing space after a # or after a list marker.
  2. Look for a missing blank line above a list, a quote or a fence.
  3. Check that every code fence and every emphasis pair is closed.
  4. Check that nested list items are indented four spaces, not two.
  5. Paste the broken section into the live preview and delete half at a time until it works.

What these mistakes share is that none of them raises an error. They simply render as something you did not intend, quietly. That is why one glance at a preview beats an afternoon of hunting for the cause.

From here, keep the Markdown syntax reference bookmarked for daily lookups and learn the GFM extensions you will meet on GitHub. When you need data in rows and columns, our tables guide covers alignment, escaping pipes and everything else tables can do.

Frequently Asked Questions

Which Markdown flavor should I learn?

Learn the core syntax in this guide first - It works everywhere - Then add GitHub Flavored Markdown (GFM), which contributes tables, task lists, strikethrough and autolinks. GFM is what GitHub, GitLab and most modern apps render, so in practice it is the de facto standard.

Why does my line break disappear when I press Enter once?

By design, a single newline does not break the line - Markdown joins adjacent lines into one paragraph. Use a blank line for a new paragraph, or end the line with two spaces (or a <br> tag) to force a break within a paragraph.

Why isn't my nested list nesting?

Almost always the indentation: two spaces is not enough for every renderer. Indent nested items by four spaces (or one tab) and nesting works everywhere. Also check that there is a blank line between the paragraph above and the first list item.

How do I make a table in Markdown?

Tables are a GFM extension using pipes (|) for columns and hyphens for the header row, with colons to control alignment. They deserve their own page - See the full Markdown tables guide with copy-paste templates.

Can I mix HTML into a Markdown document?

Yes - HTML passes straight through to the output. Use it for the few things Markdown lacks (underline, <kbd>, image sizing, collapsible <details>). Keep block-level tags separated by blank lines, and expect some platforms to strip HTML for security.

Where can I practice all of this?

In our free online Markdown editor - Paste any example from this guide and watch it render live. Nothing you type leaves your browser, and your draft autosaves locally.

How do I comment out text in Markdown?

There is no official comment syntax in Markdown, so every method is a workaround. The common one is an HTML comment, <!-- like this -->, which renderers keep out of the visible page. The other is an unused link reference definition such as [note]: # (a reminder), which prints nothing because no link points at it. Neither one is actually hidden, since anyone who opens the raw file reads it, so treat comments as notes to yourself rather than secrets.

How do I add a line break in Markdown?

End the line with two spaces, then press Enter, and the next line drops down inside the same paragraph. Those trailing spaces are invisible and plenty of editors strip them, so <br> at the end of the line is the sturdier choice. When you want a genuinely new paragraph, leave a blank line instead, which is the right answer most of the time. All three forms sit side by side in the cheat sheet.

How do I escape a character in Markdown?

Put a backslash in front of it, so \*not italic\* prints the asterisks instead of turning the words italic. It works on the characters Markdown treats as syntax: \ ` * _ { } [ ] ( ) # + - . ! plus the pipe inside tables. A backslash before an ordinary character is usually left alone, but that is a convention rather than a guarantee. For symbol-heavy text such as regular expressions, backticks are safer than counting backslashes.

What is front matter in Markdown?

Front matter is a block of settings at the very top of a Markdown file, fenced by a line of three dashes above and below. It usually holds YAML fields such as title, date and tags, and it is not part of Markdown itself; it is a convention that static site generators like Jekyll, Hugo and Astro read and then keep out of the page. A renderer that has never heard of it may print the whole block as plain text, or even draw it as a table. Those three dashes are the same ones that make a setext heading further down a file, a clash the headings guide untangles.

How do I indent a paragraph in Markdown?

You cannot, because Markdown has no paragraph-indent syntax. The trap people hit is four leading spaces: that does not indent the text, it turns the line into a code block in a monospace box. Your real options are raw HTML such as <p style="text-indent:2em">, a blockquote when a small inset is close enough, or CSS if you control the page the file is published on.

Is Markdown case sensitive?

The syntax itself is not: # Heading behaves the same whatever case you type, and code fence language labels such as Python and python are normally matched without regard to case. Two things around the syntax are case sensitive, though. Generated heading anchors are lowercased, so a link to #Setup misses the heading that actually sits at #setup. The one that really bites is file paths in links and images: Linux servers treat case as meaningful, while macOS and Windows usually forgive it, so Logo.png can look fine on your machine and break the day you publish.

Keep learning

Try the Editor

Try the Editor