Lesson 1, Topic 1
In Progress

Lesson 2.10: HTML Tables

Lesson 2.10 – HTML Tables | Knowledge Pillars HCCS
Unit 2 · Lesson 2.10

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.

HTML & CSS Web Design ~60 Minutes Beginner–Intermediate

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 colspan and vertically with rowspan
  • Style tables professionally with CSS: border-collapse, padding, zebra striping, and hover effects
  • Use the scope attribute 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:

🟦 <table>

The outer container — wraps everything. No content goes directly inside <table> except rows and sections.

🟩 <tr>

Table Row — one horizontal line of cells. All cells in a table must live inside a <tr>.

🟪 <th>

Table Header cell — bold and centered by default. Used for column or row labels. Carries semantic meaning for screen readers.

🟦 <td>

Table Data cell — the regular content cells. Left-aligned by default. Used for all non-header data.

🟧 <caption>

Optional table title — placed immediately after the opening <table> tag. Improves accessibility and gives the table a visible label.

Basic Table Example

<table> <caption>My Top 3 Movies</caption> <tr> <th>Title</th> <th>Genre</th> <th>Year</th> </tr> <tr> <td>Interstellar</td> <td>Sci-Fi</td> <td>2014</td> </tr> <tr> <td>The Dark Knight</td> <td>Action</td> <td>2008</td> </tr> <tr> <td>Spirited Away</td> <td>Animation</td> <td>2001</td> </tr> </table>
Rendered Output
My Top 3 Movies
TitleGenreYear
InterstellarSci-Fi2014
The Dark KnightAction2008
Spirited AwayAnimation2001
💡 The Golden Rule: Every row in a table should have the same number of cells. If a row has 3 headers (<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.

Table Section Elements
Element What It Groups When to Use It
<thead>The header row(s) — column labelsWhen your table has a header row you want to style distinctly or repeat on printed pages
<tbody>The main data rowsAlways — 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 bottomGreat for totals, averages, or footnotes — makes the table semantically complete
<table> <caption>Weekly Coding Hours</caption> <thead> <tr> <th scope="col">Day</th> <th scope="col">Task</th> <th scope="col">Hours</th> </tr> </thead> <tbody> <tr><td>Monday</td><td>HTML structure</td><td>1.5</td></tr> <tr><td>Wednesday</td><td>CSS styling</td><td>2.0</td></tr> <tr><td>Friday</td><td>Project work</td><td>3.0</td></tr> </tbody> <tfoot> <tr> <td colspan="2">Total Hours This Week</td> <td>6.5</td> </tr> </tfoot> </table>
Rendered Output
Weekly Coding Hours
DayTaskHours
MondayHTML structure1.5
WednesdayCSS styling2.0
FridayProject work3.0
Total Hours This Week6.5
💡 CSS Targeting Tip: Because <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.

Spanning Attributes Reference
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

<table> <tr> <!-- This one cell spans ALL 3 columns --> <th colspan="3">Game Scores This Week</th> </tr> <tr> <th>Day</th> <th>Game</th> <th>Score</th> </tr> <tr><td>Monday</td><td>Minecraft</td><td>Level 12</td></tr> <tr><td>Tuesday</td><td>Fortnite</td><td>2nd Place</td></tr> </table>
Rendered Output — colspan
Game Scores This Week
DayGameScore
MondayMinecraftLevel 12
TuesdayFortnite2nd Place

rowspan — Spanning Rows

<table> <tr> <th>Period</th> <th>Class</th> <th>Room</th> </tr> <tr> <!-- This cell spans DOWN 2 rows --> <td rowspan="2">Morning</td> <td>HTML</td> <td>Lab A</td> </tr> <tr> <!-- Only 2 cells here — "Morning" takes the first column --> <td>CSS</td> <td>Lab B</td> </tr> <tr> <td>Afternoon</td> <td>JavaScript</td> <td>Lab C</td> </tr> </table>
Rendered Output — rowspan
PeriodClassRoom
MorningHTMLLab A
CSSLab B
AfternoonJavaScriptLab C
⚠️ Critical Rule for rowspan: When a cell uses 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:

Basic borders
NameScore
Alex98
Brianna95
table { border: 1px solid #ccc; } th, td { border: 1px solid #ccc; padding: 6px 10px; }
border-collapse
NameScore
Alex98
Brianna95
table { border-collapse: collapse; } th, td { border: 1px solid #0a2463; padding: 6px 10px; }
Zebra stripes
NameScore
Alex98
Brianna95
Carlos92
tr:nth-child(even) td { background: #ddeaf8; }
Hover highlight
NameScore
Alex98
Brianna95
Carlos92
tbody tr:hover td { background: #dcfce7; }

The Full Professional CSS Starter

/* Full-width, clean table */ 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; } th { background-color: #0a2463; color: #fff; } tr:nth-child(even) td { background-color: #ddeaf8; } tbody tr:hover td { background-color: #c2d8f0; }
🎨 Why 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.

The scope Attribute Values
Value Tells the Screen Reader When to Use
scope="col"This <th> is a header for the column below itStandard column headers — use on nearly every <th> in a <thead> row
scope="row"This <th> is a header for the row it's inWhen 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 columnsWith colspan headers that label a group of columns
scope="rowgroup"This header spans a group of rowsWith rowspan headers that label a group of rows
❌ Without scope
<tr> <th>Day</th> <th>Activity</th> <th>Time</th> </tr> /* Screen reader: "Day | Activity | Time" — but no relationship info communicated for data rows */
✅ With scope="col"
<tr> <th scope="col">Day</th> <th scope="col">Activity</th> <th scope="col">Time</th> </tr> /* Screen reader: announces each cell with its column header — "Day: Monday, Activity: Coding, Time: 4:00 PM" ✓ */
♿ Rule of Thumb: If your table has column headers, add 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.
⚠️ Never Use Tables for Layout: Before CSS, developers used tables to arrange page columns, sidebars, and navigation. This is now completely outdated and harmful to accessibility — screen readers announce table structure to users, so a layout table creates confusion ("column 1 of 3, row 4 of 12" makes no sense for a navigation menu). Always use CSS Flexbox or Grid for layout, and reserve <table> strictly for actual tabular data.

🖥️ Code Skills Lab Reminder

💾 Save to Compile! As you work through today's activity in the Knowledge Pillars Code Skills Lab, press Ctrl + S (Windows) or Cmd + S (Mac) after each step. The Lab compiles on every save — watch the preview update instantly as you build and style your table!

Student Activity

📋 Build It

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.

  1. 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!
  2. 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!
  3. 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!
  4. Style your table with CSS. Inside your style.css, add rules to make your table look polished and professional. Use border-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!
  5. 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 a scope attribute (scope="col" or scope="row")
    • The <caption> element provides a clear, descriptive title for the table
    💾 Final save — commit your changes in the Code Skills Lab!

🌟 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"):

<tr> <td rowspan="2">Weekday</td> <td>Monday</td> <td>Coding</td> </tr> <tr> <!-- Only 2 cells — rowspan fills the first column --> <td>Tuesday</td> <td>Math Club</td> </tr>

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:

<tr> <th>Feature</th> <th colspan="2">HTML</th> <th colspan="2">CSS</th> </tr>

💾 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: collapse on your table CSS 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 a scope attribute — either scope="col" for column headers or scope="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) to tbody tr:nth-child(even) td — this targets only rows inside <tbody>, leaving your <thead> styles unaffected.

Lesson Summary

Lesson 2.10 — HTML Tables Reference
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: collapseCSS — merges adjacent borders into a single clean line
tr:nth-child(even) tdCSS — applies zebra striping to alternating rows
tbody tr:hover tdCSS — 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> with scope="col" or scope="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 colspan on 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) td so 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