HTML-Based Web Development
Senior 4 · ICT
HTML-Based Web Development
Lesson 4 of 5
25 min
A school timetable, a league table, exam results, a bus company's departure times: whenever information has rows and columns and you read it by finding where a row meets a column, it is tabular data, and HTML has a family of elements for it. In Senior 2 you built tables in a word processor and a spreadsheet. On the web, a table is written cell by cell, row by row.

Definition
Table (<table>): a grid of data in rows and columns.
Table row (<tr>): one horizontal row of cells. HTML builds a table row by row; there is no element for a column.
Data cell (<td>, table data): one cell holding a value. Header cell (<th>, table header): a cell that labels a column or row; shown bold and centred by default.
Caption (<caption>): the table's title. It must be the first thing inside <table>.
| Element | Role |
|---|---|
<table> | Contains the whole table |
<caption> | The title, first child of the table |
<thead> | The group of header rows at the top |
<tbody> | The group of rows with the main data |
<tfoot> | The group of summary rows at the bottom, such as totals |
<tr> | One row; contains <th> and <td> cells |
<th scope="col">, <th scope="row"> | A header for a column or for a row; scope tells screen readers which cells it labels |
<td> | One data cell |
The order of nesting is always table → (thead, tbody, tfoot) → tr → th or td. Text never goes directly inside <tr>: it must be in a cell.
Run it. The <style> element in the head holds CSS, which you will learn in Unit 6: they draw the borders and padding, because an HTML table has no borders by default. (Old pages wrote border="1" on the table; that attribute is obsolete.) Each student's name is a row header (scope="row"), so a screen reader announces "Grace, Mathematics, 91" instead of just "91". Check the averages: ICT (78 + 64 + 85 + 71) ÷ 4 = 298 ÷ 4 = 74.5; Mathematics (82 + 70 + 91 + 58) ÷ 4 = 301 ÷ 4 = 75.25.
A cell can stretch across several columns or down several rows, like "Merge cells" in a spreadsheet.
colspan="n" makes a cell n columns wide. The same row then has n − 1 fewer cells.rowspan="n" makes a cell n rows tall. Each of the next n − 1 rows then has one cell fewer, in that column.Run it and compare the result with the grid diagram. Change rowspan="2" to rowspan="1" and run again: the 08:40 row now has a gap on the right and Physics slides under Monday, because the row has one cell too few. Every row must account for every column.
Worked example 1: does every row add up?
A table should have 5 columns. Check these rows.
<tr><td colspan="2">A</td><td>B</td><td>C</td><td>D</td></tr> <tr><td rowspan="2">E</td><td>F</td><td colspan="3">G</td></tr> <tr><td>H</td><td>I</td><td>J</td></tr>
Row 1: 2 + 1 + 1 + 1 = 5 columns. Correct.
Row 2: E (1) + F (1) + G (3) = 5 columns. Correct. E has rowspan="2", so it also fills column 1 of row 3.
Row 3: 1 column is already taken by E, so it needs 4 cells, but it has only H, I and J: 1 + 3 = 4 columns. One cell is missing. Add a fourth <td> to row 3 (or give one of its cells colspan="2").
Worked example 2: from facts to a table
Rwanda is divided into four provinces and the City of Kigali. The City of Kigali has 3 districts, the Northern Province 5, the Southern Province 8, the Eastern Province 7 and the Western Province 7. Present this as a table with a total.
Plan: 2 columns (area, number of districts); a header row in <thead>; one row per area in <tbody>; the total in <tfoot>: 3 + 5 + 8 + 7 + 7 = 30.
<table>
<caption>Districts of Rwanda by province</caption>
<thead>
<tr><th scope="col">Area</th><th scope="col">Districts</th></tr>
</thead>
<tbody>
<tr><th scope="row">City of Kigali</th><td>3</td></tr>
<tr><th scope="row">Northern Province</th><td>5</td></tr>
<tr><th scope="row">Southern Province</th><td>8</td></tr>
<tr><th scope="row">Eastern Province</th><td>7</td></tr>
<tr><th scope="row">Western Province</th><td>7</td></tr>
</tbody>
<tfoot>
<tr><th scope="row">Total</th><td>30</td></tr>
</tfoot>
</table>To see it, replace the table in the code box above with this one, keep the <style> element, and run it.
Dave Gray: How to Create Tables in HTML. Watch the table, thead, tbody and tfoot elements being built up, header cells with scope, and how colspan and rowspan merge cells.
Warning
colspan or rowspan; one extra or missing <td> pushes the rest of the row out of line.Tip
Draw it first. Before writing a table with merged cells, sketch the grid on paper, shade the merged cells, and count the cells you must write in each row. Then write one <tr> per row of your sketch.
Practice
<tr><td rowspan="3">X</td><td>1</td><td>2</td></tr>. How many cells must rows 2 and 3 each contain?scope="row" adds for a blind user, using the marks table as an example.<th> (b) <tfoot> (c) <caption> (d) <tr><table><caption>Price list (illustrative)</caption><thead><tr><th scope="col">Item</th><th scope="col">Price (Frw)</th></tr></thead><tbody><tr><td>Notebook</td><td>500</td></tr><tr><td>Pen</td><td>200</td></tr><tr><td>Ruler</td><td>300</td></tr></tbody></table>, laid out one row per line.<tfoot><tr><th scope="row">Total</th><td>1000</td></tr></tfoot>: 500 + 200 + 300 = 1,000 Frw.<tr><th colspan="4">Title</th></tr><nav> for the menu and <main> for the text, placed side by side with CSS.Key points
table → thead/tbody/tfoot → tr → th/td.<caption> titles the table; <th scope="col"> and <th scope="row"> label columns and rows for everyone, including screen-reader users.colspan="n" widens a cell across n columns, rowspan="n" extends it down n rows; every row must still cover every column.Sources and credits
This lesson adapts CC BY-SA material and is shared under CC BY-SA 4.0.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Term 1 marks</title>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #999; padding: 6px 12px; text-align: center; }
thead, tfoot { background: #eef3f8; }
</style>
</head>
<body>
<table>
<caption>Term 1 marks out of 100 (illustrative)</caption>
<thead>
<tr>
<th scope="col">Student</th>
<th scope="col">ICT</th>
<th scope="col">Mathematics</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">Aline</th><td>78</td><td>82</td></tr>
<tr><th scope="row">Eric</th><td>64</td><td>70</td></tr>
<tr><th scope="row">Grace</th><td>85</td><td>91</td></tr>
<tr><th scope="row">Jean</th><td>71</td><td>58</td></tr>
</tbody>
<tfoot>
<tr><th scope="row">Class average</th><td>74.5</td><td>75.25</td></tr>
</tfoot>
</table>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>S4 timetable</title>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #999; padding: 8px 14px; text-align: center; }
th { background: #eef3f8; }
</style>
</head>
<body>
<table>
<caption>S4 timetable, first periods (illustrative)</caption>
<tr>
<th scope="col">Time</th>
<th scope="col">Monday</th>
<th scope="col">Tuesday</th>
<th scope="col">Wednesday</th>
</tr>
<tr>
<th scope="row">08:00</th>
<td rowspan="2">Mathematics</td>
<td>English</td>
<td>ICT</td>
</tr>
<tr>
<th scope="row">08:40</th>
<!-- no Monday cell here: Mathematics fills it -->
<td>Physics</td>
<td>Kinyarwanda</td>
</tr>
<tr>
<th scope="row">09:20</th>
<td colspan="3">Break</td>
</tr>
</table>
</body>
</html>Quick check
Put the table elements in nesting order, from the outermost to the innermost.
Tap the cards in the right order.