RizTech Academy logo
RizTech Academy
HTML FoundationsLesson 6 of 925 min

Lists and tables used correctly

Lists are the most under-used element in HTML and tables are the most abused. Both problems come from the same place: people pick them for how they look instead of what they are.

Three kinds of list

<ul>                          <!-- unordered: sequence does not matter -->
  <li>Atta</li>
  <li>Rice</li>
  <li>Dal</li>
</ul>

<ol>                          <!-- ordered: sequence is part of the meaning -->
  <li>Add items to the basket</li>
  <li>Enter your address</li>
  <li>Pay</li>
</ol>

<dl>                          <!-- description: name and value pairs -->
  <dt>Opening hours</dt>
  <dd>7am to 10pm, every day</dd>
  <dt>Delivery</dt>
  <dd>Free above ₹500</dd>
</dl>

The reason to care: a screen reader announces "list, 3 items" and then "item 1 of 3". That count is real information. A stack of <div>s gives none of it, and a person listening has no idea how long the list is or where they are in it.

<ol> versus <ul> is a meaning decision, not a visual one. Steps in a recipe are ordered. Items in a shop are not. If reordering them would change what the content says, it is an <ol>.

Only <li> inside a list

<!-- invalid -->
<ul>
  <p>Our stock:</p>
  <li>Atta</li>
</ul>

A <ul> or <ol> may contain only <li> elements. Put the intro line before the list. Inside an <li>, though, you can put anything — paragraphs, images, another list:

<ul>
  <li>
    <h3>Atta</h3>
    <p>₹450 per 10kg</p>
  </li>
</ul>

Nesting goes inside the <li>

<!-- wrong: the sub-list is a sibling of the li -->
<ul>
  <li>Grains</li>
  <ul><li>Atta</li></ul>
</ul>

<!-- right -->
<ul>
  <li>Grains
    <ul>
      <li>Atta</li>
      <li>Rice</li>
    </ul>
  </li>
</ul>

The wrong version renders almost identically, which is why it survives. It is invalid, and the relationship it describes is not the one you meant.

This is the convention, and it exists for a reason:

<nav>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/prices/">Prices</a></li>
    <li><a href="/contact.html">Contact</a></li>
  </ul>
</nav>

A screen reader user hears "navigation, list, 3 items" and immediately knows the size of your menu. Loose <a> tags in a <nav> tell them nothing.

The bullets and the vertical stacking come off in one CSS rule, which you will write in module 3:

nav ul {
  list-style: none;
  display: flex;
  gap: 1rem;
  margin: 0;
  padding: 0;
}

Do not avoid the list because of the bullets. Remove the bullets.

Useful <ol> attributes

<ol start="4">          <!-- begins at 4 -->
<ol reversed>           <!-- counts down -->
<ol type="a">           <!-- a, b, c — also i, I, A -->
<li value="10">         <!-- this item is 10, the rest follow -->

start is genuinely useful when a list is split across two blocks of prose.

Tables

A table is for data with two dimensions — rows that mean something and columns that mean something. Prices per weight. Delivery charges per area. Opening hours per day.

<table>
  <caption>Delivery charges by area</caption>
  <thead>
    <tr>
      <th scope="col">Area</th>
      <th scope="col">Charge</th>
      <th scope="col">Minimum order</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Kothrud</th>
      <td>Free</td>
      <td>₹300</td>
    </tr>
    <tr>
      <th scope="row">Karve Nagar</th>
      <td>₹20</td>
      <td>₹500</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td colspan="3">Charges revised 1 October 2026.</td>
    </tr>
  </tfoot>
</table>

That is more markup than you expected, and every piece of it does a job.

<caption> is the table's title, and it must be the first child of <table>. A screen reader reads it when it reaches the table, so the user knows what they are about to hear. It is the single most valuable thing on this list.

<th> versus <td>. <th> is a header cell, <td> is data. Note that the area names are <th scope="row"> — the first column is a header too, because "Kothrud" labels its row.

scope="col" and scope="row" say which cells a header governs. This is what lets a screen reader announce, when you land on a cell, "Karve Nagar, Charge, ₹20" instead of just "₹20". Without scope, a table of numbers read aloud is a list of numbers with no labels — completely useless. Two attributes are the difference between a usable table and an unusable one.

<thead>, <tbody>, <tfoot> group the rows. <thead> repeats on each page when printed, which matters for a price list somebody will print and pin up.

colspan and rowspan merge cells. Use them sparingly — a heavily merged table is very hard to navigate non-visually.

Never use a table for layout

Before CSS had layout tools, people built entire page layouts out of nested tables. You will still find it in email templates, where it remains genuinely necessary because email clients are twenty years behind.

On the web it is wrong. A screen reader announces "table, 6 rows, 4 columns" and tries to read your page as data. The user has to work out that it is not a table at all.

You have Flexbox and Grid, which are two modules away and better at this in every respect.

And do not use a list for tabular data

The opposite mistake, and more common now:

<!-- this is a table pretending not to be -->
<ul>
  <li>Kothrud — Free — ₹300 minimum</li>
  <li>Karve Nagar — ₹20 — ₹500 minimum</li>
</ul>

Three columns of data in one string. A screen reader user cannot ask "what is the minimum for Karve Nagar?" — they must listen to the whole line and parse the dashes. If your content has rows and columns, it is a table.

Tables on a phone

A four-column table does not fit in 360 pixels. The simplest honest fix:

.table-wrap {
  overflow-x: auto;
}
<div class="table-wrap">
  <table>…</table>
</div>

The table scrolls sideways inside its own box instead of breaking the page layout. It is not elegant, and it is far better than the alternatives beginners reach for — shrinking the text to 9px, or hiding columns so mobile users get less information than everybody else.

Check your work

Why a list beats divs. The screen reader announces the item count and position.

<ol> versus <ul>. Whether reordering would change the meaning.

What may go directly inside <ul>. Only <li> — but an <li> can hold anything.

Where a nested list goes. Inside the parent <li>, not beside it.

Why navigation is a list. So a user hears how many items your menu has.

What <caption> does, and where it goes. Names the table; first child of <table>.

What scope buys. "Karve Nagar, Charge, ₹20" instead of "₹20".

Why the first column is often <th>. It labels its row.

Why not tables for layout. A screen reader announces and reads your page as data.

Why not lists for tabular data. Columns collapse into one string that cannot be queried.

How to handle a wide table on a phone. Wrap it and let it scroll, rather than shrinking or hiding data.

Practice

  1. Mark up your shopping list as a <ul> and a recipe as an <ol>. Justify each.
  2. Build a <dl> for a shop's opening hours and delivery terms.
  3. Put a <p> directly inside a <ul> and run the page through an HTML validator at validator.w3.org. Read the error.
  4. Nest a list incorrectly, then correctly. Compare the rendered output.
  5. Convert a <nav> of loose links into a list. Remove the bullets with CSS.
  6. Build the delivery-charges table with caption, thead, tbody and scope.
  7. Remove the scope attributes and listen to a row with a screen reader. Put them back and listen again.
  8. Remove the <caption> and note what a screen reader says when it reaches the table.
  9. Take the "table pretending not to be" list and convert it to a real table.
  10. Build a six-column table and open it on a 360px viewport. Then wrap it in an overflow-x: auto container.
  11. Print-preview a long table with and without <thead>. Note the difference.
  12. Find a real site using a table for layout, or a list for tabular data. They are both common.

Official documentation

Next: forms, which is where HTML stops being decorative.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship