Skip to content

Lists

docs.scrimba.com

A surprising amount of a web page is really a list: navigation menus, search results, product grids, a recipe's ingredients, the steps to reset a password. Whenever content is a set of related items, HTML has an element that says so, and using it means the browser, and anyone reading with assistive software, knows the items belong together.

Unordered lists

When the order of the items does not matter, you use an unordered list. Think of a shopping list: milk, bread, eggs. It does not matter which one you write first, they are a bunch of things grouped together.

You build one with two tags. The <ul> tag wraps the whole list, and each item goes inside its own <li> tag ("list item"):

html
<ul>
  <li>Milk</li>
  <li>Bread</li>
  <li>Eggs</li>
</ul>

The browser shows each item on its own line with a bullet point in front of it. You did not add the bullets or the line breaks yourself, the browser does that because you told it this is a list.

An unordered list is a group of items where sequence carries no meaning. The <ul> element is the container, and every direct child is an <li> element holding one item:

html
<ul>
  <li>Free shipping over £40</li>
  <li>Returns within 30 days</li>
  <li>Two-year warranty</li>
</ul>

One rule to keep straight: the only thing allowed as a direct child of <ul> is <li>. You do not put a <p> or a <div> straight inside the <ul>. If you want a paragraph or a link in an item, it goes inside the <li>, which can hold any content you like.

The bullets and indentation are default browser styling, not part of the meaning. When you reach CSS you will change or remove them freely, and the list is still a list underneath.

An unordered list marks a set of items whose sequence is not significant. <ul> is the container and each item is an <li>; the only permitted direct children of <ul> are <li> elements (and script-supporting elements like <template>), so putting a bare <div> between the <ul> and its items is invalid and the parser will not treat it as part of the list.

html
<ul>
  <li>Free shipping over £40</li>
  <li>Returns within 30 days</li>
  <li>Two-year warranty</li>
</ul>

The bullet marker and the indentation are presentational defaults from the browser's built-in stylesheet, not semantics; list-style: none removes them without changing what the element means. That split matters because the meaning lives in the element, the bullet is only paint. Assistive software (the umbrella term for tools like screen readers, which speak the page aloud for people who cannot see it) reads the structure, not the marker: it announces that a list is starting and how many items it holds. We come back to what that announcement buys you in the Deep Dive on nesting.

JunoUnordered lists Use <ul> for a list where the order does not matter, and wrap each item in its own <li>. The browser adds the bullets and the line breaks for you, so you never type those by hand. It took me a while to trust that, I kept wanting to add the dots myself.
JunoUnordered lists<ul> is the container, and its only direct children are <li> items. Anything richer, a link, a paragraph, goes inside the <li>, never loose in the <ul>. The bullets are default styling you can change later, so pick the element for its meaning, not its look.
JunoUnordered lists<ul> takes only <li> as a direct child, so a stray <div> in there is invalid markup. The bullet is from the browser stylesheet, not the semantics, so list-style: none strips the dot and keeps the list. That distinction is the whole reason a screen reader can still count the items after your CSS hides the markers.

Ordered lists and their attributes

Sometimes the order is the whole point. Steps in a recipe, positions in a race, instructions you follow one after another. For those you use an ordered list, which numbers the items for you.

It works exactly like an unordered list, but the container is <ol> instead of <ul>:

html
<ol>
  <li>Preheat the oven to 180°C</li>
  <li>Mix the flour and sugar</li>
  <li>Pour into the tin and bake for 25 minutes</li>
</ol>

Now the browser puts 1, 2, 3 in front of the items instead of bullets. If you add a new step in the middle later, the numbers renumber themselves. You never type the numbers, and that is the point of using <ol>.

An ordered list is for items whose sequence carries meaning. Same structure as <ul>, swap the container to <ol>, and the browser numbers each <li> automatically. Because the numbers are generated, inserting or removing an item renumbers the rest for free.

<ol> has three attributes worth knowing. start sets the number the list begins from:

html
<ol start="4">
  <li>Whisk the eggs</li>
  <li>Fold in the flour</li>
</ol>

That list runs 4, 5. It is useful when a list continues after some other content has split it in two.

type changes the numbering style: 1 for numbers (the default), A or a for letters, I or i for Roman numerals.

html
<ol type="a">
  <li>Gather your evidence</li>
  <li>Write the summary</li>
</ol>

That list runs a, b instead of 1, 2. And reversed counts downwards, which suits a countdown or a top-five ranking read from the bottom up:

html
<ol reversed>
  <li>Bronze</li>
  <li>Silver</li>
  <li>Gold</li>
</ol>

An ordered list marks items whose sequence is meaningful; <ol> numbers each <li> from generated values, so the ordinals are never content you type and stay correct as items move.

Three attributes control the numbering. start sets the first ordinal as an integer regardless of the display style, so start="4" on a Roman-numeral list begins at iv. type sets the marker style: 1, A, a, I, or i. reversed (a boolean attribute, meaning its presence alone is the value, with nothing to set it to) counts down from the item count to 1.

html
<ol type="I" start="4" reversed>
  <li>Foundations</li>
  <li>Rollout</li>
  <li>Review</li>
</ol>

There is a distinction the styling can hide. type changes only how the marker is drawn; the value announced to assistive software and used by start is always the underlying integer. So a list styled with letters is still positions 1, 2, 3 underneath. When the ordinal is part of the content itself, a legal clause numbered for citation, prefer letting <ol> own the number over hard-coding it in the text, because a hard-coded "3." in an <li> will not renumber and a screen reader will read it twice: once as the generated marker and once as the text. One more control worth knowing: the value attribute on an individual <li> overrides that one item's number and the list continues counting from there.

JunoOrdered lists and their attributes Reach for <ol> when the order matters, like steps in a recipe, and the browser numbers the items for you. Add or remove a step and the numbers fix themselves, so you never type 1. or 2. by hand. Same <li> items as before, only a different container.
JunoOrdered lists and their attributes<ol> gives you three handy attributes: start to begin from a number other than 1, type for letters or Roman numerals instead of digits, and reversed to count down. They all still let the browser generate the numbers, which is the reason you use an ordered list in the first place.
JunoOrdered lists and their attributestype only restyles the marker; the value behind it, the one start uses and assistive tech reads, is always the integer. So do not hand-type "3." into an <li>, or it will not renumber and a screen reader says the number twice. Let <ol> own the count, and use value on a single <li> when you need to nudge one item.

Description lists

There is a third kind of list for when each item is really a pair: a word and its meaning, a label and its value. A glossary is the clearest example, each term followed by what it means.

This is a description list, and it uses three tags. <dl> wraps the whole thing, <dt> holds each term, and <dd> holds the description that goes with it:

html
<dl>
  <dt>HTML</dt>
  <dd>The language that describes the structure of a page.</dd>
  <dt>CSS</dt>
  <dd>The language that describes how the page looks.</dd>
</dl>

The browser shows each description indented under its term, so the pairing is clear at a glance. Think of "dt" as the word you are defining and "dd" as the definition.

When each item is a pairing rather than a standalone thing, a description list fits better than <ul> or <ol>. It is built from three elements: <dl> is the container, <dt> is a term, and <dd> is the description tied to it.

html
<dl>
  <dt>Author</dt>
  <dd>Priya Anand</dd>
  <dt>Published</dt>
  <dd>14 March 2024</dd>
  <dt>Reading time</dt>
  <dd>6 minutes</dd>
</dl>

That example shows the second big use beyond glossaries: key/value metadata, a set of labels and their values. The grouping is flexible. One <dt> can be followed by several <dd> elements when a term has more than one description, and several <dt> elements can share a single <dd> when several terms mean the same thing. Reach for a <dl> whenever your content is naturally "this, and its detail", and reach for <ul> or <ol> when the items stand on their own.

A description list associates names with values. <dl> is the container; <dt> is a term (the name); <dd> is a description (the value). The structure the browser and assistive software understand is a sequence of groups, where a group is one or more <dt> elements followed by one or more <dd> elements.

html
<dl>
  <dt>Storage</dt>
  <dt>Capacity</dt>
  <dd>256 GB SSD</dd>

  <dt>Colour</dt>
  <dd>Graphite</dd>
  <dd>Silver</dd>
</dl>

That flexibility is the point: many terms can share one description, and one term can carry many descriptions. Two practical fits stand out. The first is term-and-definition content such as a glossary. The second is key/value metadata, a specification table or a set of post details, where each label pairs with its value; a <dl> says "these are name/value pairs" in a way a stack of <div>s never does. Note the naming quirk: <dt> and <dd> historically stood for "definition term" and "definition description", but the modern spec deliberately broadened them to any name/value association, not dictionary definitions alone. If your content is a flat list of peers with no pairing, a <dl> is the wrong tool and <ul> is the honest one.

JunoDescription lists A description list is for pairs: a term and its meaning. Use <dl> around the whole thing, <dt> for each term, and <dd> for the description that follows it. A glossary is the picture to keep in your head, word then definition, word then definition.
JunoDescription lists Reach for <dl> when each item is a pairing, a term and its detail or a label and its value, not a set of peers. The grouping bends to fit: one <dt> can take several <dd>s, and several <dt>s can share one <dd>. Key/value metadata like author and date is where it earns its place beyond glossaries.
JunoDescription lists<dl> means name/value pairs, and the spec widened <dt> and <dd> past dictionary definitions to any label-and-value, so metadata is fair game. Many terms can share a value and one term can hold many values, which a wall of <div>s can never express. If the items are peers with nothing to pair, that is a <ul>, not a <dl>.

Nesting lists

Lists can go inside other lists. A menu might have "Drinks" with hot and cold options underneath, and each of those with its own choices. Putting one list inside another is called nesting.

The key thing to get right: the inner list goes inside an <li>, not floating between items. You put the whole <ul> (or <ol>) inside the list item it belongs to:

html
<ul>
  <li>Drinks
    <ul>
      <li>Coffee</li>
      <li>Tea</li>
    </ul>
  </li>
  <li>Snacks</li>
</ul>

"Coffee" and "Tea" are part of "Drinks", so their little list lives inside the "Drinks" item. The browser indents the inner list a bit more, so you can see it belongs to the item above it.

To show that some items belong under another item, you nest a list inside a list. The rule that keeps it valid: a nested list is a child of an <li>, never a direct child of the outer <ul> or <ol>. The inner list sits inside the item it expands on.

html
<ul>
  <li>Fruit
    <ul>
      <li>Apples</li>
      <li>Bananas</li>
    </ul>
  </li>
  <li>Vegetables
    <ul>
      <li>Carrots</li>
      <li>Spinach</li>
    </ul>
  </li>
</ul>

You can mix types as you nest, an <ol> inside a <ul> or the reverse, and nest as deep as the content honestly goes. The most common place you meet nested lists in real code is a navigation menu with submenus: a top-level list of sections, each with its own list of pages inside its <li>.

Nesting is how hierarchy is expressed: a sublist is placed inside the <li> it belongs to, never as a direct child of the parent <ul> or <ol>. Getting that wrong (a <ul> sitting directly inside another <ul>) is invalid, and the parser's recovery is not guaranteed to match what you pictured.

Nesting is also where the structure pays off for people who cannot see the indentation. A screen reader announces a list, its item count, and its nesting depth as it enters each level: "list, three items", then on a sublist "list, two items", and it tells the user when they move out again. That running commentary is the non-visual equivalent of the indentation your eye reads for free, and it only exists if the nesting is real markup rather than items pushed over with spacing.

That leads to the honesty test for any list. A navigation menu (the set of links that moves a visitor between pages, the row along the top of most sites) is a list of destinations, so the semantically correct markup is a <ul> of links, usually inside a <nav> element that labels the region as navigation. Use a real list when the content is a set of related items, whatever the CSS later makes it look like. Do not use one when it is not: faking gaps between unrelated blocks by wrapping them in <li>s to borrow list spacing tells assistive software "these three things are a group of three" when they are not. The marker can be hidden and the layout can be anything; the choice of <ul>, <ol>, or <dl> should always answer what the content honestly is.

JunoNesting lists To put a list inside a list, drop the inner one inside the <li> it belongs to, not loose between items. The browser indents it so you can see it is part of the item above. That inside-the-<li> rule is the one thing to remember here.
JunoNesting lists A nested list is always a child of an <li>, never a direct child of the outer <ul> or <ol>. You can mix an <ol> inside a <ul> and go as deep as the content really is. Navigation menus with submenus are where you will hit this most, so it is worth getting comfortable now.
JunoNesting lists Put the sublist inside its <li>, because a screen reader reads out the item count and the nesting depth, and that spoken structure is what replaces the indentation for someone who cannot see it. A nav menu really is a list of links, so mark it as one; do not wrap unrelated blocks in <li>s to borrow the spacing. Let the element say what the content honestly is, and style it however you want afterwards.