Lists: ul, ol and the nesting rule
Three quarters of the lists on the web are navigation menus. The rest are steps, ingredients, features and terms. HTML has three list elements, and one structural rule that almost everyone breaks the first time they nest one.
ul and ol differ by meaning, not by markers
<ul>
<li>Chai</li>
<li>Coffee</li>
<li>Lassi</li>
</ul>
<ol>
<li>Boil the water</li>
<li>Add the tea</li>
<li>Add the milk</li>
</ol>
<ul> is an unordered list: the items belong together, and the order
carries no meaning. Swap the drinks around and nothing is lost.
<ol> is ordered: the sequence is part of the information. Add the milk
before the tea and you have made something else.
The bullets and numbers are just the default styling. The question to ask is
not "do I want numbers?" — CSS can put numbers on a <ul> or strip them off an
<ol> in one line. The question is whether reordering the items would change
the meaning. A screen reader announces "list, three items" either way, and
announces the position for an ordered list, which is genuinely useful when the
position matters and noise when it does not.
<ol> takes a couple of attributes worth knowing:
<ol start="4"> <!-- begins at 4, for a list continued across a page -->
<ol reversed> <!-- counts down -->
<ol type="a"> <!-- a, b, c instead of 1, 2, 3 -->
The nesting rule
Here is the one that costs everybody an hour.
The only element allowed as a direct child of <ul> or <ol> is <li>.
Not a <div>, not a <p>, and — this is the surprise — not another <ul>.
So a nested list does not go between the <li> elements:
<!-- wrong -->
<ul>
<li>Routes</li>
<ul>
<li>Karachi</li>
<li>Lahore</li>
</ul>
<li>Fares</li>
</ul>
It goes inside the <li> it belongs to:
<!-- right -->
<ul>
<li>
Routes
<ul>
<li>Karachi</li>
<li>Lahore</li>
</ul>
</li>
<li>Fares</li>
</ul>
Read the correct version aloud and the structure is obvious: "Routes, and inside Routes there are two cities." The broken version says the cities are siblings of Routes, which is not what the design shows and not what you meant.
The reason it survives so long undetected is that the broken version usually
looks fine. The browser renders both with an indented sub-list, because
browsers repair invalid markup rather than refusing to display it. What differs
is everything downstream: the accessibility tree reports a different structure,
nav > ul > li selectors match different elements, and any script walking the
list gets a different answer. It also fails validation, which is the fastest
way to find it — the W3C validator flags this immediately.
The <li> may contain anything. Paragraphs, headings, images, another list,
several of them:
<ul>
<li>
<h3>Morning route</h3>
<p>Departs at 07:00 from Saddar.</p>
</li>
</ul>
All of that is legal, because it is inside the <li>.
Navigation is a list
A row of links is an unordered list of destinations, and marking it up as one is worth doing:
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/routes">Routes</a></li>
<li>
<a href="/fares">Fares</a>
<ul>
<li><a href="/fares/daily">Daily</a></li>
<li><a href="/fares/monthly">Monthly</a></li>
</ul>
</li>
</ul>
</nav>
A screen reader now announces "navigation, list, three items", and the reader
knows how much menu there is before stepping into it. Loose <a> elements in a
<div> give them none of that.
The default bullets and indentation are removed in one rule, which is why the markup costs you nothing visually:
nav ul {
list-style: none;
padding-left: 0;
margin: 0;
}
One caution: in Safari, list-style: none can make VoiceOver stop announcing
the element as a list. If that matters to you, list-style-type: "" keeps the
list semantics intact while hiding the markers.
Description lists
<dl> pairs terms with descriptions — glossaries, specifications, metadata:
<dl>
<dt>Departure</dt>
<dd>07:00 from Saddar</dd>
<dt>Duration</dt>
<dd>About three hours</dd>
<dt>Fare</dt>
<dd>450 rupees one way</dd>
<dd>800 rupees return</dd>
</dl>
<dt> is the term, <dd> the description. The relationship is many-to-many:
one term may have several descriptions, as the fare does here, and several
terms may share one description.
Two structural notes. <dt> and <dd> are direct children of <dl>, with no
wrapper around each pair — unless you want one, in which case a <div> around
a term-and-description group is explicitly allowed, and is the only place a
<div> is legal inside a list. And a <dd> is indented by default, which you
will usually want to reset.
Use <dl> when you genuinely have name-value pairs. A two-column table of data
with headers is a <table>; a list of features is a <ul>.
Fake lists
The commonest markup smell in this area:
<!-- not a list -->
<p>• Chai</p>
<p>• Coffee</p>
<p>• Lassi</p>
Three unrelated paragraphs that happen to start with a character that looks like a bullet. Nothing knows they belong together. A screen reader reads three paragraphs and says "bullet" out loud each time. Copy and paste them and the bullets come along as text. Style them and you have to reproduce indentation and hanging alignment by hand.
The same applies to a <div> per item with a background image standing in for
a marker, and to a sequence of headings used because the items happened to
need bold text.
If the items form a set, use a list element. You get the semantics free, and the styling is one CSS rule either way.
Choosing, quickly
- Would reordering change the meaning? →
<ol>. Otherwise →<ul>. - Are they term-and-definition pairs? →
<dl>. - Is it rows and columns of data? → not a list; that is a table.
- Is it one continuous passage of prose? → not a list; those are paragraphs.
Next: images, where alt text turns out to be the same kind of
problem as link text — something read entirely out of context. If you are
building the navigation above, links covers the <a> element
inside each <li>.
Prefer learning with a teacher?
WasiLearn Academy runs small-group classes covering this material, online and in Karachi.
See the classes