Lesson 2.10: HTML Tables
HTML Tables
Go beyond the basics — master table sections, spanning cells, CSS styling, accessibility with scope, and when (and when not) to use tables in modern web design.
Lesson Objectives
- Build a correctly structured HTML table using
<table>,<tr>,<th>, and<td> - Add a table title using the
<caption>element - Organize tables semantically with
<thead>,<tbody>, and<tfoot> - Merge cells horizontally with
colspanand vertically withrowspan - Style tables professionally with CSS:
border-collapse, padding, zebra striping, and hover effects - Use the
scopeattribute on<th>for accessible, screen-reader-friendly tables - Understand when tables are appropriate — and when to use CSS layout instead
Part 1: The Anatomy of an HTML Table
An HTML table is made of nested elements — each one serving a specific role. Think of it like a spreadsheet written in code: the outer container holds rows, and rows hold cells. Here are the core building blocks:
The outer container — wraps everything. No content goes directly inside <table> except rows and sections.
Table Row — one horizontal line of cells. All cells in a table must live inside a <tr>.
Table Header cell — bold and centered by default. Used for column or row labels. Carries semantic meaning for screen readers.
Table Data cell — the regular content cells. Left-aligned by default. Used for all non-header data.
Optional table title — placed immediately after the opening <table> tag. Improves accessibility and gives the table a visible label.
Basic Table Example
| Title | Genre | Year |
|---|---|---|
| Interstellar | Sci-Fi | 2014 |
| The Dark Knight | Action | 2008 |
| Spirited Away | Animation | 2001 |
<th>), every data row needs exactly 3 <td> cells. Mismatched cell counts cause columns to shift and display incorrectly.
Part 2: Table Sections — <thead>, <tbody>, <tfoot>
For more complex tables, HTML provides three semantic grouping elements. These aren't required for simple tables, but they give your table meaningful structure — helping browsers, screen readers, and your CSS target different parts of the table independently.
| Element | What It Groups | When to Use It |
|---|---|---|
<thead> | The header row(s) — column labels | When your table has a header row you want to style distinctly or repeat on printed pages |
<tbody> | The main data rows | Always — wraps all your data rows. Browsers add it implicitly anyway, so it's best practice to include it explicitly |
<tfoot> | Summary or total row(s) at the bottom | Great for totals, averages, or footnotes — makes the table semantically complete |
| Day | Task | Hours |
|---|---|---|
| Monday | HTML structure | 1.5 |
| Wednesday | CSS styling | 2.0 |
| Friday | Project work | 3.0 |
| Total Hours This Week | 6.5 | |
<thead>, <tbody>, and <tfoot> are separate elements, you can style them individually in CSS: thead tr for header styles, tbody tr:nth-child(even) for zebra striping, and tfoot td for total/summary row styles — all without conflicting selectors.
Part 3: Spanning Cells — colspan & rowspan
Sometimes a single cell needs to stretch across multiple columns or rows — like a title header spanning a whole table, or a category label that covers several rows. HTML provides two attributes for this: colspan and rowspan.
| Attribute | What It Does | Example Syntax |
|---|---|---|
colspan="n" |
Makes a cell span n columns horizontally. The row needs fewer cells to compensate. | <th colspan="3">Title</th> |
rowspan="n" |
Makes a cell span n rows vertically. Subsequent rows need one fewer cell in that column. | <td rowspan="2">Data</td> |
colspan — Spanning Columns
| Game Scores This Week | ||
|---|---|---|
| Day | Game | Score |
| Monday | Minecraft | Level 12 |
| Tuesday | Fortnite | 2nd Place |
rowspan — Spanning Rows
| Period | Class | Room |
|---|---|---|
| Morning | HTML | Lab A |
| CSS | Lab B | |
| Afternoon | JavaScript | Lab C |
rowspan="2", the next row must have one fewer <td> — the spanning cell is already occupying that position. Forgetting this is the most common spanning mistake and causes columns to shift sideways.
Part 4: Styling Tables with CSS
By default, HTML tables have no visible borders, inconsistent spacing, and minimal visual hierarchy. A few CSS rules transform a plain table into something clean and professional. Here are the four patterns you'll use most on real projects:
| Name | Score |
|---|---|
| Alex | 98 |
| Brianna | 95 |
| Name | Score |
|---|---|
| Alex | 98 |
| Brianna | 95 |
| Name | Score |
|---|---|
| Alex | 98 |
| Brianna | 95 |
| Carlos | 92 |
| Name | Score |
|---|---|
| Alex | 98 |
| Brianna | 95 |
| Carlos | 92 |
The Full Professional CSS Starter
border-collapse: collapse? By default, HTML tables have a gap between cell borders — giving you a doubled-up "double border" look. Setting border-collapse: collapse merges adjacent borders into a single clean line, which is almost always what you want for a polished table design.
Part 5: Accessible Tables — The scope Attribute
Screen readers navigate tables differently from how your eyes do — they announce each cell along with its associated header. Without accessibility attributes, users relying on screen readers may hear a cell's value but have no idea which column or row it belongs to. The scope attribute on <th> fixes this.
| Value | Tells the Screen Reader | When to Use |
|---|---|---|
scope="col" | This <th> is a header for the column below it | Standard column headers — use on nearly every <th> in a <thead> row |
scope="row" | This <th> is a header for the row it's in | When the first cell of each row is a label (like a day of the week or a person's name) |
scope="colgroup" | This header spans a group of columns | With colspan headers that label a group of columns |
scope="rowgroup" | This header spans a group of rows | With rowspan headers that label a group of rows |
scope="col" to every <th>. If the first cell in each data row is itself a label (like a day name or person's name), use <th scope="row"> instead of <td> for those cells too. This two-minute habit makes your tables accessible to all users.
<table> strictly for actual tabular data.
🖥️ Code Skills Lab Reminder
Student Activity
Add a Styled Table to Your Webpage — Code Skills Lab
Open your index.html in the Code Skills Lab. You're going to add a real data table to your personal webpage — building it step by step, adding sections, a spanning header, and then styling it professionally with CSS.
-
Choose your data and create the basic table structure. Decide what data fits your personal page — a weekly schedule, top movies, a skills list, or a comparison chart all work great. Start with a
<caption>and a header row using<th>.<section id="my-table"> <h2>My Weekly Schedule</h2> <table> <caption>Activities This Week</caption> <tr> <th scope="col">Day</th> <th scope="col">Activity</th> <th scope="col">Time</th> </tr> </table> </section>💾 Save — you should see an empty table with column headers! -
Add semantic sections and data rows. Wrap your header row in
<thead>, add at least 3 data rows inside<tbody>, and add a summary row inside<tfoot>.<table> <caption>Activities This Week</caption> <thead> <tr> <th scope="col">Day</th> <th scope="col">Activity</th> <th scope="col">Time</th> </tr> </thead> <tbody> <tr><td>Monday</td><td>Practice Coding</td><td>4:00 PM</td></tr> <tr><td>Wednesday</td><td>Watch Tutorials</td><td>6:00 PM</td></tr> <tr><td>Friday</td><td>Work on Website</td><td>3:30 PM</td></tr> </tbody> <tfoot> <tr> <td colspan="3">3 activities scheduled this week</td> </tr> </tfoot> </table>💾 Save — you should see all three rows of data and a footer row! -
Add a colspan spanning header. Above your column headers, insert a new row with a single
<th colspan="3">that acts as a group title spanning the full width of the table.<thead> <!-- Spanning title row --> <tr> <th colspan="3" scope="colgroup">My Weekly Planner</th> </tr> <!-- Regular column headers --> <tr> <th scope="col">Day</th> <th scope="col">Activity</th> <th scope="col">Time</th> </tr> </thead>💾 Save — the spanning title should stretch across all three columns! -
Style your table with CSS. Inside your
style.css, add rules to make your table look polished and professional. Useborder-collapse, padding, a header background color, and zebra striping./* Full-width with clean borders */ table { width: 100%; border-collapse: collapse; margin-top: 16px; font-family: Arial, sans-serif; } th, td { padding: 12px 16px; border: 1px solid #ccc; text-align: left; } thead th { background-color: #0a2463; color: #fff; } tr:nth-child(even) td { background-color: #ddeaf8; } tbody tr:hover td { background-color: #c2d8f0; } tfoot td { font-weight: 700; background-color: #f4f7fb; border-top: 2px solid #0a2463; }💾 Save — your table should now look clean, professional, and polished! -
Verify your work. Check that all three structural requirements are met before moving on:
- Every data row has the same number of cells as your header row
- All
<th>elements have ascopeattribute (scope="col"orscope="row") - The
<caption>element provides a clear, descriptive title for the table
🌟 Bonus Challenges
Push your table skills further:
1. Add a rowspan for grouped data — create a table where a "Category" column uses rowspan to group related rows together (like grouping Monday and Tuesday under "Weekday"):
2. Create a comparison table — build a second table comparing two or three options side by side (like comparing three programming languages, three games, or three study habits) with a colspan group header for each side:
💾 Remember to save after each change to see the result!
Troubleshooting Tips
-
Columns are shifting sideways? A row has the wrong number of cells. Count your
<th>headers and confirm every<tr>in<tbody>has exactly the same number of<td>elements. One missing or extra<td>throws off the entire layout. -
Double borders appearing? You're missing
border-collapse: collapseon yourtableCSS rule. Without it, each cell has its own individual border, creating a gap between cells that looks like double lines. -
rowspan is pushing cells out of place? When a cell uses
rowspan="2", the next row must have one fewer<td>. The spanning cell already occupies one column in that row. Forgetting this is the most common rowspan mistake. -
caption not appearing above the table? Make sure
<caption>is the very first element inside<table>— placed directly after<table>and before any<thead>or<tr>. -
Accessibility validator flagging your table? Check that all
<th>elements have ascopeattribute — eitherscope="col"for column headers orscope="row"for row headers. Also verify your table has a<caption>element describing what the data represents. -
Zebra stripes applying to header row too? Change your CSS selector from
tr:nth-child(even)totbody tr:nth-child(even) td— this targets only rows inside<tbody>, leaving your<thead>styles unaffected.
Lesson Summary
| Element / Attribute / Property | Purpose |
|---|---|
<table> | The outer container — wraps all table content |
<caption> | Optional title for the table — placed first inside <table> |
<tr> | Creates a horizontal row — all cells must be inside a row |
<th> | Header cell — bold and centered by default; carries semantic meaning |
<td> | Data cell — standard content cell, left-aligned by default |
<thead> | Groups the header row(s) — enables distinct CSS targeting |
<tbody> | Groups the main data rows — best practice to always include |
<tfoot> | Groups summary/total rows at the bottom of the table |
colspan="n" | Stretches a cell horizontally across n columns |
rowspan="n" | Stretches a cell vertically across n rows |
scope="col" | Tells screen readers this <th> is a column header |
scope="row" | Tells screen readers this <th> is a row header |
border-collapse: collapse | CSS — merges adjacent borders into a single clean line |
tr:nth-child(even) td | CSS — applies zebra striping to alternating rows |
tbody tr:hover td | CSS — highlights a row when the mouse hovers over it |
Knowledge Check
Read each question carefully and select the best answer. Each question has one correct response.
1. You want to add a visible title above a table that also helps screen readers identify the table's purpose. Which element should you use?
2. A table has 4 columns. You want a header that stretches across all 4 columns in the first row. Which attribute and value achieves this?
3. Which CSS property removes the double-border gap between table cells, giving a clean single-line grid appearance?
4. A row in your <tbody> has a <td rowspan="3"> in the first column. How many <td> cells should the next two rows have, compared to a normal row?
5. Which scope attribute value should you add to <th> elements in a header row that label the columns below them?
Lesson Checklist
- My table has a
<caption>element as its first child that describes the data - Header cells use
<th>withscope="col"orscope="row"— not plain<td> - All rows have the same number of cells (accounting for any colspan/rowspan)
- My table uses
<thead>,<tbody>, and optionally<tfoot>for semantic grouping - I used
colspanon at least one header cell to create a spanning group title - CSS includes
border-collapse: collapse— no double borders visible - CSS zebra stripes use
tbody tr:nth-child(even) tdso header rows are unaffected - I am using tables only for tabular data — not for page layout
- I saved my file in the Code Skills Lab after each change
