Learn
🤖 AI Hub 📰 News 📖 Dictionary 🧰 Resources
Grow
💼 Jobs 🚀 Community 🏆 Challenges
Explore by Stage
🌑 Zero 📚 Learn 🔧 Practice 🏗 Build 🚀 Launch 💰 Earn
Account
Login Sign Up
🏗️HTML 🎨CSS ⚡JavaScript 🐙Git & GitHub ⚛️React Soon 📘TypeScript Soon 🐍Python 🗄️Database & SQL Soon 🌐Web Development Soon ☁️Cloud & DevOps Soon 🤖AI & ML Soon 📄Resume Prep Soon 🎤Interview Prep Soon 💼Freelancing Soon 🚀SaaS & Side Projects Soon 🤖Automation Soon
Intermediate 🕑 16 min read 👁 3 views 📅 Oct 21, 2026

Advanced Tables: colspan, rowspan, caption and Table Groups

Build real exam timetables and marksheets with caption, thead, tbody, tfoot, scope, colspan, rowspan and colgroup, and keep complex tables readable for screen reader users.

When a Simple Table Is Not Enough

Every school notice board in India has two kinds of tables that students read with great care: the exam timetable and the marksheet. A timetable often has one date with two exam shifts, or one row that says “Study leave” across the whole width. A marksheet has headings like “Term 1” that sit above two smaller columns, and a total row at the bottom.

In the HTML Lists and Tables lesson you built simple tables with <table>, <tr>, <th> and <td>. Those are fine for neat grids where every cell is the same size. Real timetables and marksheets need more: cells that stretch across columns or rows, a title for the table, and clear sections for the header, body and total.

In this lesson you will learn the tools that make this possible, and how to keep complex tables readable for screen reader users too. Every example has a small <style> block that adds borders, so you can see the structure clearly.

caption, thead, tbody and tfoot

Start with the parts that give a table its shape:

ElementPurpose
<caption>The title of the table. It must be the first thing inside <table>.
<thead>The header rows, usually the column headings.
<tbody>The main data rows.
<tfoot>Summary rows such as totals or notes. Write it after <tbody>.

These groups do not change how the table looks by default, but they give it meaning. A screen reader can announce the caption, CSS can style each group differently, and when a long table is printed, some browsers repeat the <thead> on every page.

<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 12px; }
  thead { background: #e3f2fd; }
  tfoot { background: #fff8e1; font-weight: bold; }
</style>

<table>
  <caption>Half-Yearly Marksheet: Rohan Verma, Class 9 B</caption>
  <thead>
    <tr><th>Subject</th><th>Marks (out of 100)</th></tr>
  </thead>
  <tbody>
    <tr><td>English</td><td>78</td></tr>
    <tr><td>Hindi</td><td>84</td></tr>
    <tr><td>Mathematics</td><td>91</td></tr>
    <tr><td>Science</td><td>88</td></tr>
  </tbody>
  <tfoot>
    <tr><td>Total</td><td>341</td></tr>
  </tfoot>
</table>

Run it and notice the caption above the table, the blue header and the highlighted total row. Change thead in the style block to a different colour to see that the whole group changes together.

Header Cells with scope

A <th> cell is a heading, but a heading for what? In a class result table, “Maths” is the heading of a column, and a student’s name is the heading of a row. The scope attribute says which one:

  • scope="col": this heading describes the cells below it.
  • scope="row": this heading describes the cells to its right.

Sighted users understand this by looking. A screen reader user cannot see the layout, so the screen reader uses scope to read a cell like “Fatima, Science, 19” instead of just “19”.

<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 12px; }
  th[scope="col"] { background: #e3f2fd; }
  th[scope="row"] { background: #f1f8e9; text-align: left; }
</style>

<table>
  <caption>Unit Test Marks, Class 9 B</caption>
  <thead>
    <tr>
      <th scope="col">Student</th>
      <th scope="col">Maths</th>
      <th scope="col">Science</th>
    </tr>
  </thead>
  <tbody>
    <tr><th scope="row">Rohan</th><td>18</td><td>17</td></tr>
    <tr><th scope="row">Fatima</th><td>20</td><td>19</td></tr>
    <tr><th scope="row">Karthik</th><td>16</td><td>18</td></tr>
  </tbody>
</table>

colspan: One Cell Across Several Columns

The colspan attribute makes a cell stretch across more than one column. colspan="3" means “this cell takes the space of three cells”. It is perfect for a “Grand Total” label that should sit under several columns.

The important rule: every row must add up to the same number of columns. Here the table has 4 columns. The total row has one cell with colspan="3" and one normal cell, and 3 + 1 = 4.

<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 12px; text-align: center; }
</style>

<table>
  <caption>Science Marks with Practicals</caption>
  <thead>
    <tr>
      <th scope="col">Subject</th>
      <th scope="col">Theory (80)</th>
      <th scope="col">Practical (20)</th>
      <th scope="col">Total (100)</th>
    </tr>
  </thead>
  <tbody>
    <tr><th scope="row">Physics</th><td>64</td><td>18</td><td>82</td></tr>
    <tr><th scope="row">Chemistry</th><td>70</td><td>19</td><td>89</td></tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row" colspan="3">Grand Total (out of 200)</th>
      <td>171</td>
    </tr>
  </tfoot>
</table>

Change colspan="3" to colspan="2" and run it. The total moves to the wrong column and a gap appears at the end. This is the most common table bug.

rowspan: One Cell Down Several Rows

rowspan works the same way, but downwards. In a practical exam timetable, one date often has a morning and an afternoon exam. Instead of writing the date twice, give its cell rowspan="2".

The tricky part is the next row. Because the date cell already fills the first column of the next row, you must leave that cell out of the next row. The next row starts directly with “Afternoon”.

<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 12px; }
</style>

<table>
  <caption>Practical Exam Timetable</caption>
  <thead>
    <tr><th scope="col">Date</th><th scope="col">Shift</th><th scope="col">Subject</th></tr>
  </thead>
  <tbody>
    <tr>
      <th scope="rowgroup" rowspan="2">Monday 1 March</th>
      <td>Morning</td><td>Physics</td>
    </tr>
    <tr>
      <td>Afternoon</td><td>Chemistry</td>
    </tr>
    <tr>
      <th scope="rowgroup" rowspan="2">Wednesday 3 March</th>
      <td>Morning</td><td>Biology</td>
    </tr>
    <tr>
      <td>Afternoon</td><td>Computer Science</td>
    </tr>
  </tbody>
</table>

Here the date headings use scope="rowgroup", which tells assistive technology that the heading belongs to the group of rows it spans, not only to the first one.

Plan on paper first. Before writing a complex table, draw it in your notebook as a grid. Mark which cells span two boxes. Then count the cells in each row. Five minutes with a pencil saves an hour of confusion.

colgroup and col: Style Whole Columns

HTML builds tables row by row, so there is no column element in the body. To style a whole column at once, use <colgroup> with one <col> for each column, placed just after the caption. A <col> can cover several columns with span.

Only a few CSS properties work on columns: mainly background, width, border (when borders are collapsed) and visibility. You cannot change text colour or alignment through <col>. For those, style the cells themselves.

<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 12px; }
  .marks { background: #f3e5f5; }
  .total { background: #fff3e0; width: 120px; }
</style>

<table>
  <caption>Term Marks, Class 9 B</caption>
  <colgroup>
    <col>
    <col span="2" class="marks">
    <col class="total">
  </colgroup>
  <thead>
    <tr><th scope="col">Student</th><th scope="col">Term 1</th><th scope="col">Term 2</th><th scope="col">Total</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">Rohan</th><td>341</td><td>356</td><td>697</td></tr>
    <tr><th scope="row">Fatima</th><td>372</td><td>380</td><td>752</td></tr>
  </tbody>
</table>

Making Tables Accessible

A screen reader reads a table cell by cell. Without good structure, a marksheet becomes a confusing list of numbers. Follow these rules:

  • Give every data table a <caption> so users know what it is before they enter it.
  • Use <th> for every heading and add scope. Use <td> for data only.
  • Avoid empty heading cells. Write a short word like “Subject” instead.
  • Keep tables as simple as possible. If a table needs many levels of spanning headers, consider splitting it into two smaller tables. For very complex tables, HTML also offers the headers attribute, which links a cell to the id values of its heading cells.
  • Use tables only for data in rows and columns, never for page layout. Use CSS Flexbox or Grid for layout.

Real-Life Example: A Class 10 Pre-Board Timetable

Here is a full exam timetable. It uses a two-row header where “Exam Shift” spans the Morning and Afternoon columns, a study leave row that spans both shifts, an English exam that spans two days, and a footer note across the whole table. It is wrapped in a <div> with overflow-x: auto, so on a small phone the table scrolls sideways instead of breaking the page.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Class 10 Pre-Board Exam Timetable</title>
  <style>
    body { font-family: sans-serif; padding: 16px; }
    .scroll { overflow-x: auto; }
    table { border-collapse: collapse; min-width: 480px; }
    caption { font-weight: bold; font-size: 1.1em; margin-bottom: 8px; }
    th, td { border: 1px solid #444; padding: 8px 10px; text-align: center; }
    thead th { background: #1565c0; color: #fff; }
    tbody th { background: #e3f2fd; }
    .off { background: #eeeeee; font-style: italic; }
    tfoot td { text-align: left; font-size: 0.9em; }
  </style>
</head>
<body>
  <div class="scroll">
    <table>
      <caption>Sunrise Public School: Class 10 Pre-Board Exams</caption>
      <colgroup>
        <col>
        <col>
        <col span="2">
      </colgroup>
      <thead>
        <tr>
          <th scope="col" rowspan="2">Date</th>
          <th scope="col" rowspan="2">Day</th>
          <th scope="colgroup" colspan="2">Exam Shift</th>
        </tr>
        <tr>
          <th scope="col">Morning (9:30 to 12:30)</th>
          <th scope="col">Afternoon (2:00 to 4:00)</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">1 March</th><td>Monday</td>
          <td>Mathematics</td><td>Painting (practical)</td>
        </tr>
        <tr>
          <th scope="row">2 March</th><td>Tuesday</td>
          <td class="off" colspan="2">Study leave, no exam</td>
        </tr>
        <tr>
          <th scope="row">3 March</th><td>Wednesday</td>
          <td>Science</td><td>Computer Applications</td>
        </tr>
        <tr>
          <th scope="row">4 March</th><td>Thursday</td>
          <td rowspan="2">English (two papers, one each day)</td>
          <td>None</td>
        </tr>
        <tr>
          <th scope="row">5 March</th><td>Friday</td>
          <td>Hindi</td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <td colspan="4">Report 30 minutes before the shift. Bring your admit card and a blue pen.</td>
        </tr>
      </tfoot>
    </table>
  </div>
</body>
</html>

Count the cells in the 4 March and 5 March rows. The 5 March row has only three cells, because the English cell from the row above fills its morning column.

Try these changes:

  1. Add a row for 8 March (Monday) with Social Science in the morning and no afternoon exam.
  2. Make the 1 March afternoon painting exam a full-day exam by deleting the Mathematics cell and giving Painting colspan="2".
  3. Give the second <col> a class with a light background colour to highlight the Day column.

Common Mistakes (and How to Fix Them)

  • Rows that do not add up. A row with too many or too few cells pushes data into the wrong column. Fix: count each row, adding the colspan values, and make every total match.
  • Forgetting to remove cells under a rowspan. The row below gets one extra cell that sticks out on the right. Fix: delete one cell from each row that the rowspan covers.
  • Putting the caption in the wrong place. A <caption> after the rows is not valid. Fix: make it the very first child of <table>.
  • Using bold <td> instead of <th>. It looks like a heading but screen readers do not know it is one. Fix: use <th> with scope.
  • Expecting <col> to change text colour. It will not. Fix: use a class on the cells, or a CSS selector such as td:nth-child(2).

Practice Task: A Two-Term Report Card

Build a report card for one student with this structure:

  • A caption with the student’s name and class
  • A two-row header: “Subject” spans two rows, and “Term 1” and “Term 2” each span two columns
  • Under each term, the columns “Theory” and “Practical”
  • Three subject rows with scope="row" headings
  • A <tfoot> total row
<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 10px; text-align: center; }
</style>

<!-- TODO 1: Add a caption with the student's name and class -->
<!-- TODO 2: Header row 1: Subject (rowspan 2), Term 1 (colspan 2), Term 2 (colspan 2) -->
<!-- TODO 3: Header row 2: Theory, Practical, Theory, Practical -->
<!-- TODO 4: Three subject rows, with the subject as th scope="row" -->
<!-- TODO 5: A tfoot row with the totals of each column -->
<table>

</table>
Show one possible answer (try it yourself first!)
<style>
  table { border-collapse: collapse; }
  th, td { border: 1px solid #555; padding: 6px 10px; text-align: center; }
  thead { background: #e8eaf6; }
  tfoot { background: #fff8e1; font-weight: bold; }
</style>

<table>
  <caption>Report Card: Sneha Iyer, Class 11 A</caption>
  <thead>
    <tr>
      <th scope="col" rowspan="2">Subject</th>
      <th scope="colgroup" colspan="2">Term 1</th>
      <th scope="colgroup" colspan="2">Term 2</th>
    </tr>
    <tr>
      <th scope="col">Theory</th><th scope="col">Practical</th>
      <th scope="col">Theory</th><th scope="col">Practical</th>
    </tr>
  </thead>
  <tbody>
    <tr><th scope="row">Physics</th><td>58</td><td>27</td><td>62</td><td>28</td></tr>
    <tr><th scope="row">Chemistry</th><td>61</td><td>26</td><td>65</td><td>29</td></tr>
    <tr><th scope="row">Biology</th><td>66</td><td>28</td><td>63</td><td>27</td></tr>
  </tbody>
  <tfoot>
    <tr><th scope="row">Total</th><td>185</td><td>81</td><td>190</td><td>84</td></tr>
  </tfoot>
</table>

Key Takeaways

  • <caption> names the table, and <thead>, <tbody> and <tfoot> group its rows.
  • scope="col" and scope="row" tell screen readers which cells a heading describes.
  • colspan stretches a cell across columns and rowspan stretches it down rows. Every row must still add up to the same width.
  • <colgroup> and <col> style whole columns, but only a few properties such as background and width.
  • Keep tables simple, use them only for data, and wrap wide tables so they scroll on phones.

❓ Frequently Asked Questions

No, the table still works, and the browser adds a tbody for you. But using thead, tbody and tfoot makes the structure clear for styling, printing and screen readers, so it is a good habit.
Yes. A cell with colspan="2" and rowspan="2" fills a block of four cells. Remember to remove the covered cells from the rows below.
Wrap the table in a div with overflow-x: auto in CSS. The table then scrolls sideways inside its box while the rest of the page stays still.
Write tfoot after the tbody, as the last part of the table. The browser always shows it at the bottom.
Was this lesson helpful?

🎯 Finished this lesson?

🔗 Related Tutorials
🤖 Got a Doubt? Ask AI
Get instant answers in Hindi or English
🤖
Z2T Bot
Online
Ask me anything about this tutorial — I'm here! 👋