Unordered lists and the three bullet characters
An unordered list is a marker, a space, then your text. Markdown accepts three markers and treats them identically:
- Espresso
- Cortado
- Flat white
* Espresso
* Cortado
* Flat white
+ Espresso
+ Cortado
+ Flat white
- All three bullet characters render the same, so pick the hyphen and never switch.
- Numbers after the first are ignored, which is why writing every item as
1.is safer in Git. - Nest by lining the child marker up under the first letter of the parent's text.
- One stray blank line makes the whole list loose and spaces out every item.
All three produce the same bulleted list, so use the hyphen. It needs no modifier key, it never gets confused with *emphasis* in raw text, and it is what Prettier and most formatters normalize to. It is also the marker used throughout the complete Markdown guide.
Two rules that are not optional
First, the space after the marker is required. -Espresso renders as literal text, not a bullet.
Second, changing the marker starts a new list. This looks like one list but parses as two:
- Espresso
- Cortado
* Flat white
In HTML that becomes two <ul> elements, so you get an odd gap between "Cortado" and "Flat white". The behaviour is deliberate, since it is the only way to place two lists back to back. In practice it is nearly always pasted text.
Ordered lists and lazy numbering
An ordered list is a number, a period, a space, then your text:
1. Preheat the oven to 180C
2. Mix the dry ingredients
3. Fold in the melted butter
Every number after the first is ignored
Here is the part that surprises people. The renderer emits an <ol> and lets the browser count, so this renders as 1, 2, 3 as well:
1. Preheat the oven to 180C
1. Mix the dry ingredients
1. Fold in the melted butter
Writing every item as 1. is a better habit for anything stored in Git, for two reasons:
- Cleaner diffs. Insert a step into a hand-numbered list and every later line changes, so twenty modified lines hide one real edit.
- Less bookkeeping. With all-
1.numbering, adding or removing a step touches exactly one line.
Starting at a number other than 1
The first number is respected, so a list can resume where an earlier one stopped:
5. Tag the release
6. Publish the changelog
7. Announce it
That renders starting at 5. One caveat applies in CommonMark and GitHub Flavored Markdown: an ordered list starting at anything but 1 cannot interrupt a paragraph, so it always needs a blank line above it. You can also write 1) instead of 1., and switching delimiter mid-list starts a second list.
Nesting: the indentation that actually works
Nesting is where most broken lists come from, because renderers count spaces differently. The modern rule is simple. In CommonMark, which GitHub follows, a child item must be indented to at least the column where the parent's text starts.
- is two characters wide, so two spaces is enough:
- Fruit
- Apples
- Oranges
- Navel
- Blood orange
- Vegetables
1. is three characters wide, so you need three:
1. Prepare the repository
- Fork it
- Clone your fork
2. Make the change
Tip: Forget the arithmetic and line the child marker up under the first letter of the parent's text. That single habit works under - , under 1. and under a four-wide parent like 10. , and it is the one rule worth memorising from this whole syntax reference.
Two spaces or four?
Two spaces work on GitHub and every CommonMark parser. Four work everywhere, including Python-Markdown (which powers MkDocs) and the original Markdown.pl. Both of those want a full four spaces per level.
So the choice is about audience. Use two if the file only ever lives on GitHub. Use four if a site generator or an unfamiliar wiki might render it.
| Renderer | Nesting under - | Nesting under 1. | Tab characters |
|---|---|---|---|
| GitHub & GitLab (GFM) | 2 spaces | 3 spaces | Expanded to 4-column stops |
| CommonMark, markdown-it, marked | 2 spaces | 3 spaces | Expanded to 4-column stops |
| Python-Markdown (MkDocs) | 4 spaces | 4 spaces | Converted to 4 spaces |
| Original Markdown.pl | 4 spaces | 4 spaces | Treated as 4 spaces |
| Safe everywhere | 4 spaces | 4 spaces | Do not use tabs |
The tab trap
A tab is not "four spaces" to a parser. It is a jump to the next tab stop, and how far it jumps depends on the column you were already in. That causes three separate problems:
- Mix tabs and spaces in one list and levels collapse or double up unpredictably.
- Some renderers read an over-wide indent inside an item as an indented code block, so your bullet turns into grey monospace.
- The raw file looks correct in your editor and wrong in the browser, which makes it slow to debug.
Set your editor to insert spaces and the whole class of bug disappears.
Paragraphs, code and quotes inside a list item
A list item can hold any block content: several paragraphs, a fenced code block, a blockquote. The requirement never changes. Leave a blank line, then indent the extra content to the parent's content column.
A second paragraph
- Keep the commit message short.
The body can be longer, and it belongs to the same
bullet because it is indented two spaces.
- The next bullet resumes here.
A code block
Indent the fence itself, not just the code. Three spaces here, because the parent is an ordered item:
1. Install the parser:
```bash
npm install marked
```
2. Import it in your entry file.
Under-indenting the fence is the classic failure. The code block escapes the list, the list ends, and the next item restarts the numbering at 1. If your steps suddenly reset, check the indentation of the fence just above the reset. There is more on fences in the code blocks reference.
A blockquote
- The specification is blunt about this:
> A list item can contain any kind of block,
> including other lists.
Quoted material inside a list follows the marker rules from the blockquotes reference, on top of the indentation above. Tables work the same way, though a table inside a list item usually means the content wants its own section, and the tables reference covers when that is worth it.
Task lists and checkboxes
Task lists are a GFM extension: a normal bullet followed by a bracket pair. An empty pair is unchecked, an x is checked.
- [ ] Draft the release notes
- [x] Bump the version number
- [ ] Tag the release
- [x] Sign the tag
- [ ] Push it
The spacing is exact: marker, space, [, then a space or an x, then ], then a space, then the text. -[ ] fails because the marker has no space after it. - [x]Ship it fails because the bracket has none. A capital [X] is fine on GitHub, and nesting works exactly as it does for ordinary bullets.
Where the boxes are actually clickable
Rendering and interactivity are different things. The box appears far more widely than it can be ticked:
- GitHub issues, pull requests, discussions and comments. Clickable for anyone who can edit the item. Ticking one rewrites the underlying Markdown and updates the progress counter.
- A
README.mdon GitHub or GitLab. Renders, but read-only, because there is nothing to write the change back to. - Obsidian, VS Code preview and most note apps. Clickable, and the change is saved into your file.
- Static site generators. Usually rendered as disabled inputs.
- Plain CommonMark renderers. No extension at all, so readers see the literal
[ ]characters.
Check the GFM reference before you rely on task lists somewhere unfamiliar.
Loose and tight lists: why blank lines change the spacing
Two lists with identical items can render with visibly different spacing. The cause is a blank line you may not have noticed. A tight list has no blank lines between items:
- One
- Two
- Three
It compiles to compact HTML, with the text sitting directly inside each item:
<ul>
<li>One</li>
<li>Two</li>
</ul>
A loose list has a blank line somewhere inside it:
- One
- Two
- Three
Now every item is wrapped in a paragraph, and paragraph margins push the items apart:
<ul>
<li><p>One</p></li>
<li><p>Two</p></li>
</ul>
Looseness belongs to the whole list
This is the detail that catches people out:
- One stray blank line among fifteen bullets spaces out all fifteen.
- An item containing two paragraphs makes the list loose too.
- That is why a list with explanations under some items always looks airier than you meant.
Neither form is wrong. Tight suits short scannable items, loose suits items of a sentence or more. Choose deliberately and keep the blank lines consistent.
Common mistakes and their fixes
Five mistakes account for almost every list that will not render:
- No blank line before the list, so the first bullet is absorbed into the paragraph above.
- No space after the marker, which prints the dash as text.
- Nesting indented too shallowly under a number, so the child pops back to the top level.
- An un-indented note between items, which ends the list and renumbers what follows.
- Mixed markers in one list, which quietly produces several lists with gaps.
Fixing the blank line and the missing space
GitHub's parser lets a bullet list interrupt a paragraph. Python-Markdown, Markdown.pl, many wikis and most older renderers do not, and an ordered list starting at anything but 1 never can. Broken, then fixed:
Shopping list:
- Milk
- Eggs
Shopping list:
- Milk
- Eggs
The missing space is easier to spot, because the broken version renders as literal text, dash and all. Broken, then fixed:
-Milk
-Eggs
- Milk
- Eggs
Fixing indentation under a number
Two spaces is not enough for an ordered parent, so the child pops back out to the top level. Broken, then fixed with three spaces, aligned under the "P" of "Prepare":
1. Prepare the repository
- Fork it
- Clone your fork
1. Prepare the repository
- Fork it
- Clone your fork
Fixing a note that splits the list
An un-indented paragraph ends the list. What follows starts a new one and renumbers from its own first value. Broken, then fixed by indenting the note so it stays inside item 2:
1. First step
2. Second step
Remember to save your work.
1. Third step
1. First step
2. Second step
Remember to save your work.
3. Third step
Debugging a list that will not render
Paste the list into the editor and delete items until it renders. The last one you removed is the culprit, and it is nearly always a space, a blank line or an indent.
Blank lines carry real meaning in this format, which what Markdown is designed to do explains. Our Markdown reference card keeps every list form on one page, and the sibling references for links and images cover what people most often put inside list items.
Support data verified
Frequently Asked Questions
Which bullet character should I use for Markdown lists?
Use the hyphen -. All three markers render identically, but the hyphen is the community convention, is what most formatters normalize to, and never gets confused with *emphasis* in raw text. The rule that matters is consistency: switching markers mid-list creates two separate lists.
How many spaces should I indent a nested list?
Align the child marker under the first letter of the parent's text: two spaces under a - bullet, three under a 1. number. That satisfies CommonMark and GitHub. If MkDocs or an older renderer may build the file, use four spaces per level, which works everywhere. Never mix tabs and spaces.
Why does my numbered list restart at 1 in the middle?
Something ended the list and started a new one. The usual culprits are an un-indented paragraph between items, or a fenced code block whose fence was not indented to the item's content column. Indent the interrupting content to line up with the item text and the list stays intact.
Do task list checkboxes work outside GitHub?
They render anywhere that supports GitHub Flavored Markdown, including GitLab, Obsidian and VS Code. They are clickable only where the tool can write the change back, so GitHub issues are interactive while a README is read-only. Plain CommonMark renderers show the literal [ ].
Why is there extra space between my list items?
A blank line somewhere in the list made it loose, so every item is wrapped in a paragraph. Looseness applies to the entire list, not just the item you separated, so one stray blank line spaces out all of them. Remove the blank lines, or add them consistently.
Keep learning
Try the Editor
Try the Editor