Data That Has Shape
Lists are the most-used element on the web and most of them are not visibly lists. Tables are the most-abused element in its history. Learn what each is for, and the one rule that decides between them.
Lists are the most-used element on the web and most of them are not visibly lists. Tables are the most-abused element in its history. Learn what each is for, and the one rule that decides between them.
Ordered and unordered differ in meaning, not marker
Two list elements, and the choice is about whether sequence carries information.
<!-- Order does not matter: shuffle it and nothing is lost --><ul> <li>Flour</li> <li>Butter</li> <li>Sugar</li></ul> <!-- Order IS the information: step 3 after step 1 is wrong --><ol> <li>Heat the oven to 180°C.</li> <li>Rub the butter into the flour.</li> <li>Bake for 25 minutes.</li></ol>The visible markers — bullets or numbers — are a default you can change entirely in CSS. Choosing ul because you dislike numbers is answering the wrong question: ol { list-style: none; } removes them while keeping the meaning.
A third list type is genuinely useful and almost never used — a description list, for term-and-definition pairs.
<dl> <dt>HTML</dt> <dd>The structure and meaning of a page.</dd> <dt>CSS</dt> <dd>How that structure is presented.</dd></dl>Right for glossaries, metadata, specification tables, and FAQ pairs. A dt may have several dd elements, and vice versa.
Navigation is a list, and should be marked up as one
Most lists on the web do not look like lists. A navigation bar is an unordered list of links, styled into a horizontal row — and marking it up as one is worth doing even though the CSS then removes every visible trace.
<nav> <ul> <li><a href="/">Home</a></li> <li><a href="/work/">Work</a></li> <li><a href="/about/">About</a></li> </ul></nav>nav ul { list-style: none; /* no bullets */ margin: 0; padding: 0; display: flex; /* side by side */ gap: 1.5rem;}The same reasoning covers card grids, image galleries, tag rows, comment threads, and search results. If it is several of the same kind of thing, it is a list.
Only li may be a child of ul
One structural rule, broken constantly. The only permitted child of a ul or ol is an li. Anything else goes inside an li.
<ul> <ul> <p>Some text</p> <li> <li>Item</li> <p>Some text</p></ul> </li> <li>Item</li> </ul>An li can hold anything at all — paragraphs, headings, images, forms, and other lists. Nesting a list inside an li is how sub-items work, and the nested list goes inside the parent li, not between two of them.
<ul> <li>Fruit <ul> <li>Apples</li> <li>Pears</li> </ul> </li> <li>Vegetables</li></ul>Tables are for data with rows and columns
For most of the 1990s and 2000s, tables were how people laid out pages — there was nothing else. That is over, and it left tables with a bad reputation they do not deserve.
The test is one question: does a cell mean something because of the row and column it is in? If yes, it is a table. If you are only after a grid of boxes, that is CSS grid, and chapter 18.
<table> <caption>Course enrolment by year</caption> <thead> <tr> <th scope="col">Year</th> <th scope="col">Students</th> <th scope="col">Countries</th> </tr> </thead> <tbody> <tr> <th scope="row">2024</th> <td>240</td> <td>18</td> </tr> <tr> <th scope="row">2026</th> <td>810</td> <td>132</td> </tr> </tbody></table>A table
A price comparison. Match results. A timetable. Specifications. Anything where "the number in the 2026 row, Countries column" is a meaningful sentence.
Not a table
A photo gallery. A row of cards. A two-column page layout. A form. These are grids of boxes, which is a CSS problem with a CSS answer.
th and caption are what make a table readable
A table of td cells with no headers is a grid of numbers. The elements that turn it back into data are the ones people skip.
The table's title, and the first thing announced. Put it immediately after <table>. Visible to everyone — do not use a separate heading above the table instead.
A header cell rather than a data cell. Bold and centred by default, and far more importantly, announced as the heading for its row or column.
scope="col" or scope="row". This is the attribute that makes a table navigable: without it, a screen reader cannot reliably say which header belongs to which cell.
Group the header rows and the body rows. Lets a long table repeat its headers when printed, and gives you something clean to style.
Finally: a wide table on a narrow phone has no good answer, so give it one explicitly.
<div class="table-scroll"> <table> … </table></div> /* CSS */.table-scroll { overflow-x: auto; }Key takeaways
- ul when order carries no meaning, ol when it does. The bullets and numbers are a default you can remove in CSS.
- dl is for term-and-definition pairs: glossaries, metadata, FAQs.
- A nav bar is a list of links. So are card grids, galleries, and tag rows.
- Screen readers announce "list, three items", which tells a user how much is coming. A row of bare anchors announces nothing.
- The only valid child of ul or ol is li. Everything else goes inside one.
- A nested list goes inside the parent li, not between two of them.
- Use a table when a cell means something because of its row and column. Otherwise it is CSS grid.
- caption is the table's title and is announced first.
- th with scope="col" or scope="row" is what lets a screen reader say which headers a cell belongs to.
- Wrap wide tables in a container with overflow-x: auto so they scroll instead of breaking the page.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.