Unordered Lists
Unordered lists are the most frequently used syntax in Markdown: a handful of parallel points, a sequence of steps, a list of files, a set of options. The syntax itself is a single line — one character, one space — but the simplicity is deceptive. The characters are not exclusive to lists: * also marks emphasis, and both * and - can start a horizontal rule, so the one space that separates a marker from the text decides which of those you actually get. On top of that, the blank lines between items change the rendered structure and not merely the spacing, and the difference between a tight list and a loose list stays invisible until you look at the HTML.
This page builds the unordered list from the ground up. You will learn why the three markers are interchangeable but should not be mixed, why the space after the marker is a hard requirement, how blank lines turn a compact checklist into a set of separate paragraphs, what may legally live inside a list item, and where a list stops and a plain paragraph begins.
Write a hyphen, an asterisk, or a plus sign at the start of a line, follow it with a space, and put the item text after that. All three markers produce exactly the same element:
- Item 1
- Item 2
- Item 3
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
The choice is free and the parser does not care which one you use, but a single document should still stick to one marker. That is not a rule of the specification, it is a rule of readability: a reader who sees a dash group followed by an asterisk group reasonably concludes that the two groups mean different things, even though the renderer treats them as unrelated lists of equal standing. The hyphen is the safest default, because it has no second meaning anywhere else in the syntax. What matters much more is what happens when the marker changes halfway through. Changing the marker does not continue the list, it starts a new one:
- Item 1
* Item 2
The result is not one list of two items but two separate <ul> elements, one item in each, with the gap that a block boundary introduces between them. This is the most common way an unordered list gets accidentally split in half, and it nearly always happens when a list is assembled by copy and paste from two different sources.
A marker that is not followed by a space does not open a list at all. The line stays ordinary text:
-Item 1
-Item 2
The two lines stay inside a single paragraph, so the page shows the text -Item 1 and -Item 2 rather than two list items. The space is what tells the parser “this character is a marker, not content”. The very same character is used for emphasis, and the space is the only thing that separates the two readings. *emphasis* closes immediately, so it never has a space after the opening asterisk and is read as emphasis, while a * followed by a space at the start of a line is a marker. If a document is full of bulleted lists and also needs bold or italic text, the hyphen avoids the whole collision.
A blank line between two items changes the rendered structure. Without a blank line the list is tight and every item is a bare line of text, as in the first example above. With a blank line between the items the list becomes loose, and every item is wrapped in a paragraph of its own:
- Install the tool
- Configure it
<ul>
<li>
<p>Install the tool</p>
</li>
<li>
<p>Configure it</p>
</li>
</ul>
The two versions look similar on screen, but they carry different information. A tight list is one indivisible checklist: two items read as two steps of a single procedure. A loose list is two small blocks of prose that happen to be grouped, and each item may contain its own paragraphs, its own code, or its own nested list. Choose the tight form for short parallel points, and the loose form whenever an item needs a sentence or two of explanation.
A list item is not restricted to a single line of text. It can hold inline formatting, code, a nested list, and even a table. The only rule is that continuation lines have to be indented to the column where the item’s content starts, which is the third column, two columns in from the left margin:
- Run `pnpm install` to fetch the dependencies
- **Important:** keep `pnpm-lock.yaml` in the repository
- A nested list:
- first detail
- A table inside an item:
| Option | Effect |
|:-------|:-------|
| `--dry-run` | reports the plan only |
The backticks become inline code, the double asterisks become strong emphasis, and the indented dash becomes a nested <ul> inside the third item. The table works because the whole item is indented by two spaces, which puts the table on the same footing as the item’s own text instead of breaking out of the list. Keep one consequence in mind: a table needs a blank line in front of it, and a blank line anywhere inside a list makes every item in that list loose.
A list can interrupt a paragraph, and no blank line is needed in front of it. The reverse also holds: a line that follows a list item without a marker does not end the list, it is pulled into the item it follows. This is called lazy continuation, and it is the reason a paragraph you meant to write after a list sometimes disappears into the last item. To close a list, start a new paragraph with a blank line and a line that continues no item:
Here are the three steps:
- Install the dependencies
- Start the development server
This paragraph is not part of the list.
| Written as | Actually renders as | Why |
|---|---|---|
-Item | plain text | a space must follow the marker |
- Item then * Item | two separate lists | changing the marker starts a new list |
- Item then an unindented line | merged into the previous item | lazy continuation |
Changing the marker starts a new list instead of continuing the old one. Two items that were meant to be one checklist become two lists stacked on the page, and the reader sees a break where none was intended. The usual cause is copy and paste: a block pasted in from another document arrives with a different marker. When you merge two lists, check the markers first, and if you need a visual distinction between groups, express it with a subheading rather than with a different bullet character.
Rewrite the following plain text as Markdown. Use a single unordered list for the three top-level steps, keep the marker and the space after it consistent throughout, nest the two sub-steps under their own parent steps using indentation, and format the command names as inline code.
Install the dependencies
Run pnpm install
Configure the environment
Copy .env.example to .env.local
Start the development server
Approach:
The three steps are parallel points, so they belong in one unordered list written with a single marker. The marker must be followed by a space, otherwise the whole line degrades into plain text. The sub-step under the first item is a child of that item, and the only way to express a child is to indent it to the column where the parent’s content begins. The parent marker occupies two columns, so the child needs two spaces of indentation.
Steps:
- Write each of the three steps as a hyphen, a space, and the text, using the same hyphen for all three so that they stay in one list.
- Put the two command names in backticks so that they render as inline code rather than as prose.
- Indent each sub-step by exactly two spaces and give it its own hyphen and space, so that it lines up with the column where the parent item’s text starts.
- Check the result: rendering it must produce a three-item list in which the first and second items each hold one nested item, and the third item is a plain sibling of them.
Answer:
- Install the dependencies
- Run `pnpm install`
- Configure the environment
- Copy `.env.example` to `.env.local`
- Start the development serverYou can now write an unordered list with any of the three markers, and you know that the space after the marker is what makes a line a list rather than a paragraph. You know that mixing markers silently splits a list in two, that blank lines between items switch a list between tight and loose, that a list item can hold code, a nested list, and a table, and that an unindented line after a list item is pulled into that item by lazy continuation. The next page covers the sibling syntax: ordered lists, where the renderer takes over the numbering entirely, so that the 1. you type in the source has no effect on the number the reader sees.
