Last time, in Part 4, we covered how to embed images and media in a page. Today we’re covering something you’ll use in almost every single page you ever build: lists.
If you’ve ever written a recipe, a to-do list, a table of contents, or a set of instructions, you’ve already thought in lists. HTML gives you dedicated elements for this instead of forcing you to fake it with line breaks and dashes, and using the real elements matters more than it might seem — it affects accessibility, styling, and even how search engines understand your content.
Why not just use paragraphs with dashes?
You could write something like this:
<p>- Milk<br>- Eggs<br>- Bread</p>
It looks like a list visually, but to the browser, a screen reader, or a search engine, it’s just one paragraph of text with some dashes and line breaks in it. There’s no way for software to know “this is a list of 3 items.” A screen reader user navigating this page has no idea they can jump between list items, and no announcement like “list, 3 items” is made. Real list elements fix this by giving the content actual semantic meaning — meaning a computer can understand, not just meaning a human can infer from visual layout.
Unordered lists: when order doesn’t matter
Use <ul> (unordered list) when the sequence of items doesn’t matter — a shopping list, a set of features, a list of links in a navigation menu. Each item goes inside an <li> (list item) tag.
<ul>
<li>Milk</li>
<li>Eggs</li>
<li>Bread</li>
</ul>
By default, browsers render this with bullet points (•) and indentation — but that’s just the default styling, not a rule. You can restyle it completely with CSS (including removing the bullets entirely), and it’s still semantically a list underneath.
Ordered lists: when sequence matters
Use <ol> (ordered list) when the position of each item matters — step-by-step instructions, a ranking, a recipe’s steps. It also uses <li> for each item, but the browser numbers them automatically.
<ol>
<li>Preheat the oven to 200°C</li>
<li>Mix the dry ingredients</li>
<li>Fold in the wet ingredients</li>
<li>Bake for 25 minutes</li>
</ol>
This renders as:
- Preheat the oven to 200°C
- Mix the dry ingredients
- Fold in the wet ingredients
- Bake for 25 minutes
Note that you never write the numbers yourself — the browser generates “1.”, “2.”, “3.”… automatically based on the order of the <li> elements. If you reorder the <li> tags in your HTML, the numbers update automatically too.
Useful <ol> attributes
A few attributes let you control an ordered list’s numbering without touching CSS:
<ol start="5">
<li>Step five</li>
<li>Step six</li>
</ol>
<ol reversed>
<li>Third place</li>
<li>Second place</li>
<li>First place</li>
</ol>
<ol type="A">
<li>Option A</li>
<li>Option B</li>
</ol>
start sets the first number, reversed counts down instead of up (useful for rankings, e.g. “3rd, 2nd, 1st”), and type switches the numbering style — 1 (default), A/a (letters), or I/i (roman numerals).
Description lists: for term-value pairs
There’s a third, less commonly taught list type: the description list, written <dl>. It’s built for pairing a term with its description — a glossary, an FAQ, metadata like a product’s specifications.
<dl>
<dt>HTML</dt>
<dd>The markup language used to structure content on the web.</dd>
<dt>CSS</dt>
<dd>The language used to style and lay out HTML content.</dd>
</dl>
Here, <dt> (description term) holds the term being defined, and <dd> (description details) holds its definition. You can even attach multiple descriptions to one term:
<dl>
<dt>Python</dt>
<dd>A high-level programming language known for readability.</dd>
<dd>Also the name of a large, non-venomous snake.</dd>
</dl>
A lot of beginners never learn about <dl> and end up faking glossaries with headings or bold text — but when your content is genuinely a set of term/definition pairs, <dl> is the semantically correct choice, and it’s supported by every browser.
Nesting lists
Lists can contain other lists — for example, a table of contents with sub-sections, or a recipe with a sub-list of ingredient variations. A nested list goes inside the parent <li>, after the text (if any):
<ul>
<li>Frontend
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
</li>
<li>Backend
<ul>
<li>Node.js</li>
<li>Python</li>
</ul>
</li>
</ul>
This is a very common beginner mistake to get backwards: the nested <ul> must be placed inside the outer <li>, not as a sibling after it. If you put it after the closing </li>, it’s no longer a sub-list of that item — it becomes its own separate list at the same level, which breaks both the visual indentation and the semantic “this is a child of that item” relationship.
Visualizing the nesting structure
Here’s what the DOM tree looks like for the nested list above — notice how the inner <ul> lives one level deeper, as a child of a specific <li>, not the outer <ul>:
Lists without bullets: the navigation menu pattern
One of the most common real-world uses of <ul> has nothing to do with bullet points at all: site navigation menus. Almost every navigation bar you’ve ever seen is secretly an unordered list, styled with CSS to remove the bullets and lay the items out horizontally.
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
Why bother with a list instead of just a row of links? Because a nav menu genuinely is a list of options — semantically, a list is exactly what it is, whether or not it looks like one visually. Screen readers will announce “list, 3 items” when entering this <nav>, giving blind users a clear sense of how many navigation options exist before they start tabbing through them.
Choosing the right list type: a quick comparison
| Element | Use it for | Rendered by default as |
|---|---|---|
<ul> |
Items where order doesn’t carry meaning (features, ingredients, nav links) | Bulleted, unnumbered |
<ol> |
Items where sequence matters (steps, rankings, instructions) | Numbered (or lettered/roman with type) |
<dl> |
Term/definition or label/value pairs (glossaries, specs, FAQs) | Indented term-then-description pairs |
Common mistakes to avoid
- Using <br> instead of real list markup. Line breaks look similar visually but carry none of the semantic meaning screen readers and search engines rely on.
- Putting text directly inside <ul> or <ol>. Only
<li>elements (and, in modern HTML, scripts/templates) are valid direct children of a list — stray text or other tags placed directly inside<ul>/<ol>is invalid HTML and browsers will silently “fix” it in unpredictable ways. - Nesting the sub-list outside the <li>. As covered above, a nested list belongs inside the parent item’s
<li>, not after its closing tag. - Using <ol> for unordered content just to get numbers, then hiding them with CSS. If the numbers aren’t meaningful, use
<ul>— don’t reach for<ol>purely for the numbering and then fight the browser to hide it. - Forgetting <dl> exists. Many beginners fake glossaries with bold text and paragraphs when a description list is the exact, purpose-built element for that content.
Recap
In this lesson we covered:
- Why real list elements matter over faking lists with line breaks and dashes
<ul>for unordered items, and<ol>for ordered/sequential items, both using<li>for each entry- Controlling ordered lists with
start,reversed, andtype <dl>/<dt>/<dd>for term/definition pairs- How to correctly nest lists inside a parent
<li> - Why navigation menus are usually just a styled
<ul>under the hood
Coming up next: Part 6 will cover HTML tables — structuring genuinely tabular data with <table>, <tr>, <th>, and <td>, plus why tables should never be used for page layout.