Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT
Beginner+100 XP

Lists — ul, ol, dl

Ordered, unordered, and description lists — nesting them correctly, and the semantic reasons to choose one over the other.

25 min August 2026
// Part 01 — ul, the Unordered List

ul — A List Where Order Carries No Meaning

<ul> (unordered list) groups a set of related items where the sequence they appear in does not change what they mean. Every item lives inside a <li> (list item), and <li> is only ever valid as a direct child of <ul> or <ol> — it has no meaning sitting on its own.

A basic unordered list
<ul>
  <li>Milk</li>
  <li>Eggs</li>
  <li>Bread</li>
  <li>Coffee</li>
</ul>

"Unordered" describes the semantics, not the visual rendering — by default, browsers draw a bullet point (•) in front of each <li>, which is exactly why beginners reach for <ul> whenever they want bullet points on the page. That instinct happens to be right most of the time, but the actual reasoning should run the other direction: choose <ul> because the items form an unordered collection, and the bullet styling follows naturally from that choice — not because you want bullets and are working backward from the visual result.

💡 Note
A grocery list, a set of navigation links, a list of features on a pricing page, tags attached to a blog post — none of these change meaning if you shuffle the items. That is the test for <ul>.
// Part 02 — ol, the Ordered List

ol — A List Where Sequence Is Part of the Meaning

<ol> (ordered list) is structurally identical to <ul> — the same <li> children — but it carries a semantic promise that <ul> does not: the order of the items is meaningful. Browsers render <ol> items with numbers by default, precisely because a reader needs to know an item is "step 3," not just "one of several steps."

A basic ordered list — a recipe's steps
<ol>
  <li>Preheat the oven to 425°F</li>
  <li>Toss the vegetables in olive oil and salt</li>
  <li>Spread on a baking sheet in a single layer</li>
  <li>Roast for 25 minutes, stirring halfway through</li>
</ol>

Swap the order of a recipe's steps, a set of installation instructions, or a countdown, and the content is now wrong — not just visually rearranged, actually incorrect. That is the exact signal that <ol>, not <ul>, is the right element.

start, reversed, and type — controlling how ol counts

<ol> supports a few attributes that change how its automatic numbering behaves, useful anywhere the list does not simply start at 1 and count upward.

start — begin counting from a specific number
<ol start="5">
  <li>Fifth step</li>
  <li>Sixth step</li>
  <li>Seventh step</li>
</ol>
<!-- Useful for a list that continues a sequence broken up by other content -->
reversed — count downward instead of up
<ol reversed>
  <li>Third place</li>
  <li>Second place</li>
  <li>First place</li>
</ol>
<!-- Renders as: 3, 2, 1 -->
type — switch the numbering style (letters, roman numerals)
<ol type="A">
  <li>First option</li>
  <li>Second option</li>
</ol>
<!-- Renders as: A. First option   B. Second option -->

<ol type="i">
  <li>First</li>
  <li>Second</li>
</ol>
<!-- Renders as: i. First   ii. Second -->
🎯 Pro Tip
type here changes the actual numbering style rendered by the browser's default styles — this is distinct from the CSS list-style-type property covered briefly in Part 07, which can achieve similar visual results but through styling rather than a content attribute. Prefer the type attribute only when the letter/numeral choice is part of the content's actual meaning (a legal document's numbered sub-clauses, for example); reach for CSS when it is purely a visual preference.
// Part 03 — Choosing ul vs ol

The Real Test: Does Reordering the Items Change What They Mean?

The single question that decides between <ul> and <ol> is not "do I want bullets or numbers" — it is "if I shuffled these items into a random order, would the content still be correct?" If yes, use <ul>. If shuffling it would make it wrong, incomplete, or confusing, use <ol>.

Same visual bullets, different underlying meaning
<!-- The order genuinely doesn't matter — any arrangement is equally correct -->
<ul>
  <li>Wireless keyboard</li>
  <li>USB-C hub</li>
  <li>Laptop stand</li>
</ul>

<!-- The order IS the content — step 2 only makes sense after step 1 -->
<ol>
  <li>Unplug the device from power</li>
  <li>Hold the reset button for 10 seconds</li>
  <li>Plug the device back in</li>
</ol>

This distinction is not pedantic — screen readers announce list length and, for <ol>, each item's position ("item 2 of 4") as part of navigating the list, giving a listener genuinely useful positional information for a sequence but potentially misleading information for a collection with no real order. Search engines and other tools that parse page structure make the same distinction. Choosing the semantically correct element is not just "more correct" in an abstract sense — it changes what real software tells real users about your content.

⚠️ Important
Do not choose based on how you want it to look. If you want numbered-looking bullets on content where order genuinely does not matter, that is a job for CSS counter-* properties or list-style-type on a <ul> — not a reason to reach for <ol> and misrepresent the content's actual structure.
// Part 04 — Nesting Lists Correctly

Nested Lists — And the li-Wrapping Rule Almost Everyone Gets Wrong at First

A list can nest inside another list — a sub-list under a main topic, a multi-level outline, a site's navigation with dropdown sub-menus. The rule that trips up nearly every beginner: the nested <ul> or <ol> must live inside an <li> of the parent list, not as a sibling sitting after it.

Correct nesting — the sub-list lives inside its parent li
<ul>
  <li>
    Frontend
    <ul>
      <li>HTML</li>
      <li>CSS</li>
      <li>JavaScript</li>
    </ul>
  </li>
  <li>Backend</li>
</ul>
Incorrect — the sub-list is a sibling of li, not contained by it
<ul>
  <li>Frontend</li>
  <ul>
    <li>HTML</li>
    <li>CSS</li>
  </ul>
  <li>Backend</li>
</ul>

<!-- This is invalid HTML: ul is not a permitted child of ul directly.
     Browsers will attempt to recover, but the resulting structure is
     unpredictable and does not reflect the intended hierarchy. -->

The mental model worth keeping: a sub-list is a more detailed breakdown of one specific item, so it belongs nested where that item lives, not floating between items at the parent level. This same containment rule applies identically to <ol>, and mixed nesting — an <ol> nested inside a <ul>'s <li>, or the reverse — is completely valid whenever the semantics genuinely call for it.

Putting more than text inside an li

An <li> is not limited to plain text — it can contain paragraphs, images, links, or any other flow content, exactly like a <div> can. The common mistake is placing block-level content like a heading or a full form directly beside a nested list without a wrapper, making it genuinely ambiguous whether that content belongs to the item above it, below it, or stands on its own.

An li containing more than a single line of text
<ul>
  <li>
    <strong>Senior Frontend Engineer</strong>
    <p>Remote · Full-time · Posted 3 days ago</p>
  </li>
  <li>
    <strong>Product Designer</strong>
    <p>Austin, TX · Full-time · Posted 1 week ago</p>
  </li>
</ul>
// Part 05 — dl, dt, dd

dl — The Description List, for Term/Description Pairs

<dl> (description list — historically called a "definition list," and the name you will still hear used interchangeably) is a third, distinct list type for content that is neither a simple unordered collection nor a sequence: pairs of a term and its description. Each term is a <dt>, each description a <dd>, and both live as direct children of <dl>.

A basic description list — a glossary entry
<dl>
  <dt>Hoisting</dt>
  <dd>JavaScript's behavior of moving variable and function declarations to the top of their scope before code executes.</dd>

  <dt>Closure</dt>
  <dd>A function that retains access to variables from its enclosing scope, even after that outer function has returned.</dd>
</dl>

This is the correct element for a genuine glossary, a dictionary-style word list, or any content that is fundamentally a set of labeled definitions — not just any content that happens to look like a two-column layout. Reaching for <dl> purely because it visually resembles a design mockup, without the content actually being term/description pairs, misuses the element the same way choosing <ol> purely for its numbers misuses that one.

Metadata and key-value pairs — a second legitimate use

Beyond glossaries, <dl> is also the correct element for structured metadata — a set of labeled facts about something, where each label pairs with one value.

dl used for structured metadata
<dl>
  <dt>Author</dt>
  <dd>Maria Chen</dd>

  <dt>Published</dt>
  <dd>March 3, 2026</dd>

  <dt>Reading time</dt>
  <dd>8 minutes</dd>
</dl>
// Part 06 — Multiple Terms and Descriptions

One Term, Several Descriptions — and Several Terms, One Description

The relationship inside a <dl> is not strictly one <dt> to one <dd>. A single term can have multiple descriptions listed one after another, and a description can follow multiple consecutive terms that share it — both are valid, and both come up in real content.

One term, multiple descriptions
<dl>
  <dt>API</dt>
  <dd>Application Programming Interface.</dd>
  <dd>A set of rules that lets one piece of software communicate with another.</dd>
</dl>
Multiple terms sharing one description — synonyms
<dl>
  <dt>HTTP</dt>
  <dt>Hypertext Transfer Protocol</dt>
  <dd>The protocol web browsers and servers use to communicate — the same concept, referred to two ways.</dd>
</dl>

As with the other two list types, <dl> can be nested — a <dd> can contain its own <dl> for a sub-breakdown of a definition, following the exact same containment principle from Part 04: the nested list lives inside the element it elaborates on, not beside it.

// Part 07 — A Brief Word on CSS list-style

Styling Lists With CSS — A Preview, Not the Full Picture

The default bullets and numbers you have seen throughout this module are browser default styles, not something baked permanently into the elements — CSS can change or remove them entirely. Full CSS styling depth is out of scope for this HTML-focused module, but the single property worth knowing now is list-style-type, since you will reach for it almost immediately in real projects.

A brief preview — removing default bullets, a common navigation pattern
nav ul {
  list-style-type: none;
  padding-left: 0;
}

This exact rule is why navigation menus built from a semantically correct <ul> of links do not visually show bullet points on a real website — the markup stays semantically honest (still genuinely an unordered list of links) while CSS controls how it looks. Removing the bullets does not change what screen readers or search engines understand about the structure; the list is still announced as a list of a certain length, exactly as before.

💡 Note
Never reach for a non-list element (a plain <div> full of <span> or <p> elements) purely to avoid dealing with default bullet styling — that trades away real semantic meaning for a styling problem CSS already solves cleanly. The full CSS styling toolkit for lists, spacing, and custom markers is covered properly once you reach the CSS phases of this track.
// Part 08 — Real World
💼 What This Looks Like at Work

An Accessibility Bug Report at a Seattle SaaS Company

Scenario — B2B SaaS company, Seattle · Support ticket escalation

A customer using a screen reader files a support ticket about a project-management tool's "Setup Checklist" widget: it announces as "5 items" but reads out of order every time the page reloads, making the step-by-step onboarding instructions genuinely impossible to follow. The ticket gets escalated to engineering the same day.

The original markup
<div class="checklist">
  <div class="checklist-item">Connect your GitHub repository</div>
  <div class="checklist-item">Invite your team</div>
  <div class="checklist-item">Create your first project</div>
  <div class="checklist-item">Configure notifications</div>
  <div class="checklist-item">Complete your profile</div>
</div>

What the engineer finds

There is no list markup at all — just five sibling <div> elements styled to look like a numbered checklist with CSS ::before counters. Because nothing here is an actual <ol>, a screen reader has no idea these five blocks form a sequence, announces them as five unrelated pieces of generic content, and — since the checklist items are re-fetched from an API and rendered in whatever order the response happens to arrive — the visual numbering (drawn purely by CSS position, not tied to real content order) occasionally does not match the actual step sequence a sighted user sees either, an entirely separate bug the ticket had accidentally also surfaced.

The fix — a genuine ol, sorted server-side before rendering
<ol class="checklist">
  <li class="checklist-item">Connect your GitHub repository</li>
  <li class="checklist-item">Invite your team</li>
  <li class="checklist-item">Create your first project</li>
  <li class="checklist-item">Configure notifications</li>
  <li class="checklist-item">Complete your profile</li>
</ol>

The fix is two changes, both directly from this module: swapping the <div> wrapper and item elements for a genuine <ol>/<li> structure (Part 02), and sorting the items by an explicit step_order field from the API before rendering, rather than trusting whatever order the response happened to arrive in — since <ol>'s entire semantic promise (Part 03) is that the markup order itself is the meaningful order. The existing CSS number-styling is deleted entirely, replaced by the browser's native <ol> numbering, which now stays correctly synced to the actual content order by construction, not by two separate systems that happened to usually agree.

// Part 09 — Misconceptions

Four Misconceptions About HTML Lists

✕ ""Use ol when you want numbers, ul when you want bullets""
The choice should be driven by whether the order of the items is meaningful, not by which default marker you want. Numbers or bullets are both easily changed with the CSS list-style-type property regardless of which list element you use — the element choice is about semantics, not appearance.
✕ ""dl is an old, deprecated element from early HTML that shouldn't be used anymore""
dl (renamed "description list" from its older "definition list" framing) is fully current in the HTML specification and is the correct, semantic choice for glossaries, FAQs, and any labeled term/description or key/value content — it is simply less commonly needed than ul or ol, not deprecated.
✕ ""li elements can only contain plain text""
An li can contain any flow content — paragraphs, images, links, even another nested list — exactly like a div can. The rule that actually matters is where the li itself is allowed to live: only as a direct child of ul or ol.
✕ ""Nested lists are written as siblings, right after the li they relate to""
A nested ul or ol must be placed INSIDE the li of the parent item it elaborates on, not as a sibling that follows it. Placing it as a sibling produces invalid HTML that browsers will attempt to recover from unpredictably.
// Part 10 — Interview Prep

5 Interview Questions — With Complete Answers

What is the actual difference between ul and ol beyond the default bullet vs. number styling?
The difference is semantic, not visual: ol communicates that the order of its items is meaningful content — a sequence of steps, a ranking — while ul communicates that the items form a collection whose order carries no meaning. Screen readers and other assistive technology use this distinction, announcing positional information ("item 2 of 5") more meaningfully for an ol. Visual styling — bullets, numbers, letters, or neither — is fully controllable with CSS regardless of which element is chosen, so styling preference should never drive the choice between them.
When would you use a description list (dl) instead of ul or ol?
When the content is fundamentally a set of term/description or label/value pairs — a glossary, an FAQ, or structured metadata like a set of labeled facts about an article (author, published date, reading time). dt holds each term, dd holds its corresponding description, and both are valid HTML5 (not deprecated), just less frequently needed than ul or ol in typical page content.
What is the correct way to nest one list inside another?
The nested ul or ol must be placed inside the li element of the parent item it is elaborating on — not as a sibling of that li. This reflects the actual relationship: the sub-list is a more detailed breakdown of one specific parent item, so it structurally belongs inside that item, not floating between items at the top level.
Can an li contain more than plain text — for example, a paragraph or an image?
Yes — li accepts any flow content, the same category of content a div can contain, including paragraphs, images, links, and nested lists. The restriction that actually applies is on the li element itself: it is only valid as a direct child of ul or ol, nowhere else.
Why might a designer's numbered-looking list still be marked up as a ul rather than an ol?
Because visual appearance and semantic meaning are independent: a design can show numbers purely as a stylistic choice (via CSS counters or list-style-type) on content whose order genuinely carries no meaning — a numbered list of unordered feature highlights, for instance. The markup should reflect whether the underlying content order matters, not whether the design shows numerals.
// Common Mistakes

List Mistakes Beginners Make Constantly

Building fake lists out of styled div elements instead of real list markup
Divs styled to look like a bulleted or numbered list carry none of the semantic meaning real list elements provide — screen readers announce them as unrelated generic content instead of a list of a known length, and the visual numbering (if done purely with CSS) can drift out of sync with the actual content order, exactly as shown in the Real World example.
Placing a nested list as a sibling of li instead of inside it
A ul or ol appearing directly between li elements, rather than nested inside one of them, produces invalid HTML — ul and ol only permit li (and a few script-related elements) as direct children. Browsers recover unpredictably from this, and the resulting structure often does not match what was intended.
Choosing ol vs ul based on wanting numbers, not based on whether order matters
Numbering is a styling choice, fully controllable through CSS on either list type. The element itself should be chosen based on whether the sequence of items is genuinely part of the content's meaning — reversing the items and asking "is this still correct?" is the reliable test.
Using dl for any two-column layout, regardless of whether the content is actually term/description pairs
dl carries a specific semantic promise — a term paired with its description. Content that merely looks like two columns in a design mockup, without an actual term/description relationship, should use a more general layout element instead, not dl purely for its visual shape.
Forgetting that li, dt, and dd have required parents
li is only valid inside ul or ol; dt and dd are only valid inside dl. Placing any of these elements directly in a generic container, without their required parent, produces invalid markup that assistive technology and browsers may not interpret as a list at all.
// Error Library

Errors and Rendering Bugs You Will Hit With Lists — And Exactly Why

HTML validator: "Element ul not allowed as child of element ul in this context"
Cause: A nested ul or ol was placed directly inside a parent ul/ol as a sibling of its li elements, instead of being nested inside one specific li — the mistake covered in Part 04.
Fix: Move the nested list so it sits inside the opening and closing tags of the li it elaborates on, not between separate li elements.
HTML validator: "Element dd not allowed as child of element div in this context"
Cause: dt and dd were used outside of a dl parent — commonly, someone reaches for the shorter dt/dd tags for a generic label/value pair without realizing they require a dl wrapper to be valid.
Fix: Wrap the dt/dd pairs in a dl element, or, if the content genuinely is not a term/description relationship, use different elements — a styled div with a heading and paragraph, for instance.
Screen reader announces list content as unrelated, ungrouped text with no item count
Cause: The list is built from styled div or span elements instead of real ul/ol/li markup, so assistive technology has no structural signal that the content forms a list at all — the exact issue in the Real World example.
Fix: Replace the styled divs with genuine ul/ol and li elements. Visual appearance can be fully preserved with CSS; only the underlying markup needs to change.
List numbering visually resets or restarts unexpectedly mid-page
Cause: Multiple separate ol elements are being used where a single continuous list was intended — each ol restarts its own numbering at 1 by default unless a start attribute is explicitly set.
Fix: Either combine the items into a single ol, or, if they must remain separate elements for structural reasons, use the start attribute on later ol elements to continue the numbering where the previous one left off.

🎯 Key Takeaways

  • ul is for collections where item order carries no meaning; ol is for sequences where order is genuinely part of the content — test it by asking whether shuffling the items would make the content wrong.
  • ol supports start, reversed, and type attributes for controlling how its automatic numbering behaves — distinct from CSS-driven visual numbering styles.
  • li is only valid as a direct child of ul or ol, but can itself contain any flow content — paragraphs, images, links, even a nested list.
  • A nested list must live inside the li of the parent item it elaborates on, never as a sibling positioned between li elements — that produces invalid HTML.
  • dl (description list) pairs dt (term) with dd (description) — correct for glossaries, FAQs, and structured label/value metadata, not a deprecated element.
  • A single dt can be followed by multiple dd elements, and multiple consecutive dt elements can share one dd, for synonyms or grouped terms.
  • Visual bullet/number styling is fully controlled by CSS (list-style-type and related properties) regardless of which list element is used — never choose ul vs. ol based on appearance alone.

What comes next

Module 07 covers HTML tables — table, thead/tbody/tfoot, th, td, colspan/rowspan — and exactly why tables should never be used for page layout, a mistake with a genuinely important history behind it.

Module 07 → Tables — Structure and Correct Usage
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...