HTML Module 2 – Text, semantics and page structure
Lists: unordered, ordered and description
Choose between ul, ol and dl, number ordered lists with start, reversed, type and value, nest lists correctly and mark up terms with their descriptions.
What you will learn
- Choose between ul, ol and dl for a set of items
- Set the numbers of an ordered list with start, reversed, type and value
- Structure nested lists inside li elements, and recognise what checkers report when they are wrong
- Write description lists, including several terms for one description and groups wrapped in div
Before you start
What you’ll learn · Before you start
What you will learn
- Choose between ul, ol and dl for a set of items
- Set the numbers of an ordered list with start, reversed, type and value
- Structure nested lists inside li elements, and recognise what checkers report when they are wrong
- Write description lists, including several terms for one description and groups wrapped in div
Before you start
On this page
HTML has three kinds of list. An unordered list, <ul>, holds items whose order does not matter, such as the
ingredients of a recipe. An ordered list, <ol>, holds items whose order is part of the meaning, such as the
steps of the method or a ranking, and the browser numbers them. A description list, <dl>, pairs names with
values: terms with their definitions, or labels such as “Time” with “10 minutes”. Every item of a <ul> or an <ol>
is an <li>, and in a <dl> the names are <dt> elements and the values <dd> elements.
A list is structure, not just bullets and numbers. Browsers tell assistive technology that a list has started and
how many items it has, and screen readers let users jump from one list to the next. A list inside another list goes
inside one of its <li> items.
Choosing ul, ol or dl
The HTML Standard’s test is about meaning: an <ol> holds items put in that order on purpose, so that another order
would say something different, and a <ul> holds items that could be shuffled without changing what the page says.
So ask what happens if you shuffle the items. This recipe uses all three kinds:
<h1>Masala chai for two</h1>
<dl>
<dt>Serves</dt>
<dd>2 cups</dd>
<dt>Time</dt>
<dd>10 minutes</dd>
<dt>Equipment</dt>
<dd>A small saucepan and a strainer</dd>
</dl>
<h2>Ingredients</h2>
<ul>
<li>1 cup of water</li>
<li>1 cup of milk</li>
<li>2 teaspoons of loose black tea</li>
<li>2 teaspoons of sugar</li>
<li>2 green cardamom pods, crushed</li>
<li>A small piece of ginger, grated</li>
</ul>
<h2>Method</h2>
<ol>
<li>Boil the water with the ginger and cardamom.</li>
<li>Add the tea and let it simmer for two minutes.</li>
<li>Pour in the milk and sugar, and bring it back to the boil.</li>
<li>Strain the chai into two cups.</li>
</ol> | Criterion | <ul> | <ol> | <dl> |
|---|---|---|---|
| Order of items | Does not matter | Part of the meaning | May matter, not numbered |
| Each item is | <li> | <li> | One or more <dt>, then one or more <dd> |
| Default look | Bullets | Numbers | Values indented under names |
| Role in the accessibility tree | list | list | list (Chromium: a description list) |
| When to choose | Ingredients, features, links | Steps, rankings, clauses | Glossaries, details, questions and answers |
The roles in the table come from HTML-AAM, the specification that maps HTML elements to what browsers report to
assistive technology. Each <li> becomes a list item that knows its position and the number of items in its list,
and each <dt> and <dd> becomes a term and a definition. In Chromium 153’s accessibility tree, the recipe’s details
were a description list with three terms and three definitions, and the ingredients and the method were two lists.
That is what lets a screen reader say how many items a list has, and NVDA, for example, moves to the next list with
the L key.
Numbering an ordered list
Four attributes control the numbers of an <ol>:
startsets the first number, for examplestart="3"when steps 1 and 2 were on an earlier page.reversedcounts down. A reversed list withoutstartbegins at its number of items, so a list of four counts 4, 3, 2, 1.typechooses the kind of number:1for decimal (the default),aorAfor letters,iorIfor Roman numerals.valueon an<li>gives that item its own number, and the items after it continue from there.
The Standard defines exactly how the numbers come out: start from the list’s starting value, give each item the
current number (or its own value), then add one, or subtract one in a reversed list. This file has six lists that
test those rules, including two edge cases:
<h2>Book a ticket (continued from step 2)</h2>
<ol start="3">
<li>Choose a train and a class.</li>
<li>Add the passengers.</li>
<li>Pay and download the ticket.</li>
</ol>
<h2>Countdown to the exam</h2>
<ol reversed start="5">
<li>Revise the formulas.</li>
<li>Solve one past paper.</li>
<li>Pack the admit card and two pens.</li>
<li>Sleep early.</li>
<li>Reach the centre an hour before.</li>
</ol>
<h2>Counting down without a start</h2>
<ol reversed>
<li>Fourth</li>
<li>Third</li>
<li>Second</li>
<li>First</li>
</ol>
<h2>Jumping to a number with value</h2>
<ol>
<li>Tea</li>
<li>Coffee</li>
<li value="10">Lassi</li>
<li>Buttermilk</li>
</ol>
<h2>Letters, and numbers below 1</h2>
<ol type="a" reversed start="2">
<li>Two</li>
<li>One</li>
<li>Zero</li>
<li>Minus one</li>
</ol>
<h2>Roman numerals near their limit</h2>
<ol type="I" start="3998">
<li>Three thousand nine hundred and ninety-eight</li>
<li>Three thousand nine hundred and ninety-nine</li>
<li>Four thousand</li>
</ol> The program below applies the Standard’s rules to each <ol> of that file and writes the numbers with the counter
styles that type maps to in CSS:
// Which number each item of an <ol> gets, worked out the way the HTML Standard defines it: the list's starting value
// is its start attribute, or the number of items when the list is reversed, or 1; each item takes the current number
// (or its own value attribute), then the number goes up by one (down by one in a reversed list). The type attribute
// picks the CSS counter style that writes the number: decimal, letters (a, A) or Roman numerals (i, I). Letters cover
// only 1 and up, and Roman numerals only 1 to 3999; outside that range a number is written in decimal.
import { readFileSync } from "node:fs";
const attributesOf = (tag) =>
Object.fromEntries([...tag.matchAll(/([a-z-]+)(?:="([^"]*)")?/g)].map((m) => [m[1], m[2] ?? ""]));
const ROMAN = [[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"], [100, "C"], [90, "XC"], [50, "L"], [40, "XL"], [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"]];
function roman(n) {
let out = "";
for (const [value, letters] of ROMAN) for (; n >= value; n -= value) out += letters;
return out;
}
function letters(n) {
let out = "";
for (; n > 0; n = Math.floor((n - 1) / 26)) out = String.fromCharCode(97 + ((n - 1) % 26)) + out;
return out;
}
function marker(n, type) {
if ((type === "a" || type === "A") && n >= 1) return type === "a" ? letters(n) : letters(n).toUpperCase();
if ((type === "i" || type === "I") && n >= 1 && n <= 3999) return type === "i" ? roman(n).toLowerCase() : roman(n);
return String(n);
}
const html = readFileSync("numbering.html", "utf8");
for (const m of html.matchAll(/<ol\b([^>]*)>([\s\S]*?)<\/ol>/g)) {
const ol = attributesOf(m[1]);
const items = [...m[2].matchAll(/<li\b([^>]*)>/g)].map((li) => attributesOf(li[1]));
const reversed = "reversed" in ol;
let numbering = "start" in ol ? Number(ol.start) : reversed ? items.length : 1;
const markers = items.map((li) => {
if ("value" in li) numbering = Number(li.value);
const n = numbering;
numbering += reversed ? -1 : 1;
return `${marker(n, ol.type ?? "1")}.`;
});
const values = items.some((li) => "value" in li) ? ` (item values: ${items.map((li) => li.value ?? "-").join(", ")})` : "";
console.log(`<ol${m[1]}>${values}`.padEnd(40) + markers.join(" "));
} Output
<ol start="3"> 3. 4. 5. <ol reversed start="5"> 5. 4. 3. 2. 1. <ol reversed> 4. 3. 2. 1. <ol> (item values: -, -, 10, -) 1. 2. 10. 11. <ol type="a" reversed start="2"> b. a. 0. -1. <ol type="I" start="3998"> MMMCMXCVIII. MMMCMXCIX. 4000.
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node numbering.mjs
Chromium 153 drew exactly these markers, and its accessibility tree reported them as text, from “3. “ to “4000. “. Firefox 155 drew the same ones on screen. The last two lists show the edge cases:
- Numbers below 1 are always decimal. The Standard says so whatever the
type, so a reversed lettered list that starts at “b” continues with “a”, then “0” and “-1”. - Roman numerals stop at 3999. CSS defines the Roman counter styles only from 1 to 3999 and writes other numbers in decimal, so the third item became “4000.” rather than a Roman numeral.
Long markers can also run out of room. Markers sit in the list’s left indent, which is 40px by default, so “MMMCMXCVIII.” did not fit: both engines cut it off at the edge of the page. More padding in CSS fixes that.
One more detail matters if you read lists with JavaScript. The start property of a reversed list without a start
attribute is 1 in Chromium 153 and Firefox 155, although the list counts down from its number of items. The Standard
mentions this mismatch in a note. Read the list’s items, not start, to know where it begins.
Use type only when the kind of number is part of the content, such as a contract whose clauses are referred to as
(a), (b) and (c). When letters or Roman numerals are only decoration, set them with CSS (list-style-type) instead,
so a new design does not need new markup.
Nesting lists
The only elements a <ul> or an <ol> may contain directly are <li> items (and script-supporting elements such as
<template>). A sub-list therefore goes inside the <li> it belongs to:
<h2>Before the trip</h2>
<ul>
<li>Documents
<ul>
<li>Ticket, printed or on your phone</li>
<li>A photo identity card</li>
</ul>
</li>
<li>Food
<ul>
<li>Water bottle</li>
<li>Snacks for the journey</li>
</ul>
</li>
<li>Phone charger</li>
</ul> A sub-list lives inside an item
Text description of the diagram
The diagram draws each element as a box inside the element that contains it.
- The outer box is a ul. Directly inside it are only two li items: "Documents" and "Phone charger".
- Inside the "Documents" item, after its text, is a second ul.
- That inner ul holds two li items of its own: "Ticket" and "Photo identity card".
No ul sits directly inside another ul: the inner list always belongs to one item of the outer list.
Here is the same list built the wrong way: a paragraph straight inside the <ul>, the sub-list as a sibling of
“Documents” instead of its child, and an <li> with no list around it:
<h2>Before the trip</h2>
<ul>
<p>Pack the night before:</p>
<li>Documents</li>
<ul>
<li>Ticket, printed or on your phone</li>
<li>A photo identity card</li>
</ul>
<li>Phone charger</li>
</ul>
<li>A list item outside any list</li> The parser does not repair any of this. Chromium 153 and Firefox 155 built a DOM with exactly the structure written, so the mistakes reach assistive technology unchanged. Checkers report them:
- The Nu Html Checker 26.10.2:
Element “p” not allowed as child of element “ul” in this context., the same for the<ul>inside the<ul>, andElement “li” not allowed as child of element “main” in this context. - html-validate 11.16.1:
<p> element is not permitted as content under <ul>, the same for the inner<ul>, and<li> element requires a <ul>, <ol>, <menu> or <template> element as parent. - axe-core 4.13.0:
list(serious) on the<ul>, andlistitem(serious) on the lone<li>, in both engines.
The fixes: put the sentence “Pack the night before:” in a <p> before the list, move the sub-list inside the
“Documents” item, and put the lone item into a list.
Description lists
In a <dl>, each group is one or more names (<dt>) followed by one or more values (<dd>). Several names for one
value are synonyms, and several values for one name are alternatives. Each group may be wrapped in a <div>, for
styling, and that does not change its meaning:
<h2>Words used in this course</h2>
<dl>
<div>
<dt>Element</dt>
<dd>A part of a page, such as a paragraph or a list, written with a start tag, its content and usually an end tag.</dd>
</div>
<div>
<dt>Attribute</dt>
<dd>A name and value in a start tag that adds information to an element, such as <code>start="3"</code>.</dd>
</div>
<div>
<dt>Void element</dt>
<dt>Empty element</dt>
<dd>An element that has no content and no end tag, such as <code><br></code>.</dd>
</div>
<div>
<dt>Tag</dt>
<dd>The text that starts or ends an element, such as <code><li></code>.</dd>
<dd>In everyday speech, the element itself.</dd>
</div>
</dl> Read the groups as the Standard does: “Void element” and “Empty element” are two names for one description, and “Tag”
has two descriptions. Within one <dl>, a name should not appear twice. A description list is not only for
glossaries: the Standard names metadata (as in the recipe’s details), questions and answers, and any other name and
value pairs.
A <dt> or <dd> outside a <dl>, or anything other than <dt>, <dd>, <div> and script-supporting elements
directly inside it, breaks the list. On a test page with a <p> inside a <dl> and a <dt> and <dd> after it,
axe-core reported definition-list (serious) on the list and dlitem (serious) on both stray elements, and both
validators reported each one.
menu: a list of commands
<menu> is a list too. The Standard calls it “simply a semantic alternative to <ul>” for a toolbar of commands,
each in an <li>:
<menu>
<li><button type="button">Copy</button></li>
<li><button type="button">Cut</button></li>
<li><button type="button">Paste</button></li>
</menu> It adds no behaviour. Chromium 153 exposed this example as a list of three items containing three buttons, exactly like
a <ul>. The arrow-key handling that a real toolbar widget has comes from JavaScript and ARIA, which a later module
covers. For navigation links, use a <ul> inside a <nav> instead.
Try it: mark up a class trip
Turn these notes for a school trip into one page, with a heading for each part:
- What to bring (order does not matter): school identity card; lunch box and a water bottle; a notebook and a pencil.
- Booking the tickets: steps 1 and 2 are done by the school office; on this page the steps start at 3: each student pays the class teacher; the teacher pays for the group online; the museum sends one group ticket by email.
- Words you will hear: exhibit (an object or display shown to visitors); gallery (a room with exhibits on one theme); guide, also called docent (a person who leads visitors and explains the exhibits).
Check your page with the HTML Validator, or paste it into the HTML, CSS and JS playground and look at the numbers. One answer, which both validators and axe-core passed without a list error:
<h1>Class trip to the science museum</h1>
<h2>What to bring</h2>
<ul>
<li>School identity card</li>
<li>Lunch box and a water bottle</li>
<li>A notebook and a pencil</li>
</ul>
<h2>Booking the tickets</h2>
<p>Steps 1 and 2, choosing the date and the group size, are done by the school office.</p>
<ol start="3">
<li>Each student pays the class teacher.</li>
<li>The teacher pays for the whole group online.</li>
<li>The museum sends one group ticket by email.</li>
</ol>
<h2>Words you will hear on the trip</h2>
<dl>
<dt>Exhibit</dt>
<dd>An object or a display that the museum shows to visitors.</dd>
<dt>Gallery</dt>
<dd>A room or hall of the museum with exhibits on one theme.</dd>
<dt>Guide</dt>
<dt>Docent</dt>
<dd>A person who leads visitors around and explains the exhibits.</dd>
</dl> The packing list is a <ul>, the booking steps an <ol start="3"> so that they read 3, 4 and 5, and the words a
<dl> in which “Guide” and “Docent” share one description.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
When do you use <ol> instead of <ul>?
Use <ol> when the order of the items is part of their meaning, so that changing the order would change what the
content says: the steps of a procedure, a ranking, the clauses of a legal text. Use <ul> when the items could be
shuffled without changing anything, such as ingredients or features. The choice is not about bullets versus numbers:
CSS can show numbers on a <ul> or hide them on an <ol>. What changes is the meaning that browsers and assistive
technology get, and the extras that only <ol> has: start, reversed and type, and value on its items. A
quick test is to imagine the list read aloud in a different order. If that would mislead someone, it is an ordered
list.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A design system's stepper shows numbered steps built from div elements, with the numbers drawn by CSS counters. Screen reader users hear no list and no positions, and one checkout flow must start at step 4. How would you rebuild it, and what changes for users?
-
Mid
A FAQ page marks each question as a dt and each answer as a dd. A colleague wants each question to be an h3 with paragraphs under it instead. Which would you choose for a page of 40 questions, and why?
Key takeaways
<ul>when order does not matter,<ol>when it does,<dl>for name and value pairs.- Only
<li>items go directly inside<ul>,<ol>and<menu>; a sub-list goes inside an<li>. start,reversed,typeandvalueset the numbers; numbers below 1 are decimal, and Roman numerals stop at 3999.- A reversed list without
startbegins at its number of items, although itsstartproperty says 1. - In a
<dl>, one or more<dt>are followed by one or more<dd>; a<div>may wrap each group. - The parser keeps broken list structure as written, so checkers such as the Nu checker, html-validate and axe-core are how you find it.
References
- HTML Standard: the ol element (WHATWG)
- HTML Standard: the ul element (WHATWG)
- HTML Standard: the li element (ordinal values) (WHATWG)
- HTML Standard: the dl element (WHATWG)
- HTML Standard: the menu element (WHATWG)
- CSS Counter Styles Level 3 (W3C CSS Working Group)
- HTML Accessibility API Mappings (HTML-AAM) (W3C)
- NVDA User Guide (browse mode and single letter navigation) (NV Access)
- axe-core 4.13 rule: list (Deque Systems)
- axe-core 4.13 rule: definition-list (Deque Systems)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress