HTML5 Mastery: The Complete Web Foundation
HomeInsightsCoursesHTMLAdvanced Tables (colspan, rowspan, caption)
Tables & Data

Advanced Tables (colspan, rowspan, caption)

Master advanced table features for complex data layouts. Learn colspan, rowspan, column groups, and techniques for sophisticated table structures.

Spanning Columns (colspan)

colspan makes a cell span across multiple columns.

HTML
<table>
    <tr>
        <th colspan="3">Quarter 1 Sales Report</th>
    </tr>
    <tr>
        <th>Product</th>
        <th>Units</th>
        <th>Revenue</th>
    </tr>
    <tr>
        <td>Laptops</td>
        <td>150</td>
        <td>$150,000</td>
    </tr>
    <tr>
        <td>Tablets</td>
        <td>200</td>
        <td>$100,000</td>
    </tr>
    <tr>
        <th colspan="2">Total Revenue</th>
        <td>$250,000</td>
    </tr>
</table>

<!-- The first header spans all 3 columns --&gt;
<!-- The footer label spans 2 columns --&gt;

Spanning Rows (rowspan)

rowspan makes a cell span across multiple rows.

HTML
<table>
    <tr>
        <th>Department</th>
        <th>Employee</th>
        <th>Role</th>
    </tr>
    <tr>
        <td rowspan="3">Engineering</td>
        <td>Alice</td>
        <td>Senior Dev</td>
    </tr>
    <tr>
        <!-- Engineering cell spans here --&gt;
        <td>Bob</td>
        <td>DevOps</td>
    </tr>
    <tr>
        <!-- Engineering cell spans here --&gt;
        <td>Carol</td>
        <td>QA Lead</td>
    </tr>
    <tr>
        <td rowspan="2">Marketing</td>
        <td>Dave</td>
        <td>Manager</td>
    </tr>
    <tr>
        <!-- Marketing cell spans here --&gt;
        <td>Eve</td>
        <td>Designer</td>
    </tr>
</table>

Combining colspan and rowspan

HTML
<table>
    <tr>
        <th rowspan="2">Product</th>
        <th colspan="2">Q1</th>
        <th colspan="2">Q2</th>
    </tr>
    <tr>
        <!-- Product header spans here --&gt;
        <th>Units</th>
        <th>Revenue</th>
        <th>Units</th>
        <th>Revenue</th>
    </tr>
    <tr>
        <td>Laptops</td>
        <td>100</td>
        <td>$100k</td>
        <td>120</td>
        <td>$120k</td>
    </tr>
    <tr>
        <td>Tablets</td>
        <td>80</td>
        <td>$40k</td>
        <td>90</td>
        <td>$45k</td>
    </tr>
</table>

Column Groups (<colgroup>)

Apply styling to entire columns without repeating styles on each cell.

HTML
<table>
    <colgroup>
        <col style="background-color: #f0f0f0;">
        <col span="2" style="background-color: #e0e0e0;">
        <col style="background-color: #d0d0d0;">
    </colgroup>
    
    <thead>
        <tr>
            <th>Name</th>
            <th>Age</th>
            <th>City</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>John</td>
            <td>28</td>
            <td>NYC</td>
            <td>Active</td>
        </tr>
        <tr>
            <td>Jane</td>
            <td>34</td>
            <td>LA</td>
            <td>Active</td>
        </tr>
    </tbody>
</table>

<!-- Benefits: --&gt;
<!-- - Style columns without repeating CSS --&gt;
<!-- - Better semantic structure --&gt;
<!-- - Easier maintenance --&gt;

Using Classes with colgroup

HTML
<style>
.highlight {
    background-color: #fffacd;
}
.important {
    background-color: #ffcccc;
    font-weight: bold;
}
</style>

<table>
    <colgroup>
        <col>
        <col class="highlight">
        <col class="important">
    </colgroup>
    
    <tr>
        <th>Product</th>
        <th>Price</th>
        <th>Stock</th>
    </tr>
    <tr>
        <td>Laptop</td>
        <td>$999</td>
        <td>5</td>
    </tr>
</table>

Complex Table Example: Schedule

HTML
<table>
    <caption>Weekly Class Schedule</caption>
    <colgroup>
        <col style="width: 100px;">
        <col span="5" style="width: 150px;">
    </colgroup>
    
    <thead>
        <tr>
            <th>Time</th>
            <th>Monday</th>
            <th>Tuesday</th>
            <th>Wednesday</th>
            <th>Thursday</th>
            <th>Friday</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">9:00</th>
            <td>Math</td>
            <td rowspan="2">Science Lab</td>
            <td>Math</td>
            <td>English</td>
            <td>Math</td>
        </tr>
        <tr>
            <th scope="row">10:00</th>
            <td>English</td>
            <!-- Science Lab spans here --&gt;
            <td>History</td>
            <td>Math</td>
            <td>PE</td>
        </tr>
        <tr>
            <th scope="row">11:00</th>
            <td colspan="5" style="text-align: center; background: #f0f0f0;">
                Lunch Break
            </td>
        </tr>
        <tr>
            <th scope="row">12:00</th>
            <td>Science</td>
            <td>Art</td>
            <td>PE</td>
            <td>Science</td>
            <td>Music</td>
        </tr>
    </tbody>
</table>

Nested Tables (Use Sparingly)

Tables within tables. Generally avoid, but sometimes necessary for complex layouts.

HTML
<table>
    <tr>
        <th>Employee</th>
        <th>Projects</th>
    </tr>
    <tr>
        <td>John Doe</td>
        <td>
            <!-- Nested table --&gt;
            <table>
                <tr>
                    <th>Project</th>
                    <th>Status</th>
                </tr>
                <tr>
                    <td>Website</td>
                    <td>Active</td>
                </tr>
                <tr>
                    <td>App</td>
                    <td>Complete</td>
                </tr>
            </table>
        </td>
    </tr>
</table>

<!-- ⚠️ Warning: Nested tables are hard to maintain and less accessible --&gt;
<!-- Consider alternative layouts (CSS Grid, Flexbox) --&gt;

Sortable Tables (with JavaScript)

HTML
<table id="sortable-table">
    <thead>
        <tr>
            <th onclick="sortTable(0)">Name ↕</th>
            <th onclick="sortTable(1)">Age ↕</th>
            <th onclick="sortTable(2)">City ↕</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>John</td>
            <td>28</td>
            <td>NYC</td>
        </tr>
        <tr>
            <td>Alice</td>
            <td>34</td>
            <td>LA</td>
        </tr>
        <tr>
            <td>Bob</td>
            <td>22</td>
            <td>Chicago</td>
        </tr>
    </tbody>
</table>

<script>
function sortTable(columnIndex) {
    const table = document.getElementById('sortable-table');
    const tbody = table.querySelector('tbody');
    const rows = Array.from(tbody.querySelectorAll('tr'));
    
    // Toggle sort direction
    const isAscending = tbody.dataset.sortDir !== 'asc';
    tbody.dataset.sortDir = isAscending ? 'asc' : 'desc';
    
    // Sort rows
    rows.sort((a, b) => {
        const aText = a.cells[columnIndex].textContent;
        const bText = b.cells[columnIndex].textContent;
        
        // Try numeric comparison
        const aNum = parseFloat(aText);
        const bNum = parseFloat(bText);
        
        if (!isNaN(aNum) && !isNaN(bNum)) {
            return isAscending ? aNum - bNum : bNum - aNum;
        }
        
        // String comparison
        return isAscending 
            ? aText.localeCompare(bText)
            : bText.localeCompare(aText);
    });
    
    // Re-append sorted rows
    rows.forEach(row => tbody.appendChild(row));
}
</script>

<style>
th {
    cursor: pointer;
    user-select: none;
}
th:hover {
    background-color: #ddd;
}
</style>

Sticky Headers

HTML
<style>
.table-container {
    max-height: 400px;
    overflow-y: auto;
}

table {
    border-collapse: collapse;
    width: 100%;
}

thead th {
    position: sticky;
    top: 0;
    background-color: #2c3e50;
    color: white;
    z-index: 10;
}

tbody td {
    padding: 12px;
    border: 1px solid #ddd;
}
</style>

<div class="table-container">
    <table>
        <thead>
            <tr>
                <th>Column 1</th>
                <th>Column 2</th>
                <th>Column 3</th>
            </tr>
        </thead>
        <tbody>
            <!-- Many rows... header stays visible --&gt;
            <tr><td>Data</td><td>Data</td><td>Data</td></tr>
            <!-- ... 100 more rows ... --&gt;
        </tbody>
    </table>
</div>

Accessibility for Complex Tables

Using headers attribute

HTML
<table>
    <tr>
        <th id="name">Name</th>
        <th id="q1" colspan="2">Q1</th>
        <th id="q2" colspan="2">Q2</th>
    </tr>
    <tr>
        <th id="blank"></th>
        <th id="units-q1" headers="q1">Units</th>
        <th id="rev-q1" headers="q1">Revenue</th>
        <th id="units-q2" headers="q2">Units</th>
        <th id="rev-q2" headers="q2">Revenue</th>
    </tr>
    <tr>
        <td headers="name">Product A</td>
        <td headers="units-q1 q1">100</td>
        <td headers="rev-q1 q1">$10k</td>
        <td headers="units-q2 q2">120</td>
        <td headers="rev-q2 q2">$12k</td>
    </tr>
</table>

<!-- Screen readers announce: --&gt;
<!-- "Product A, Q1 Units: 100" --&gt;
<!-- "Product A, Q1 Revenue: $10k" --&gt;

Responsive Advanced Tables

HTML
<style>
/* Desktop: normal table */
table {
    border-collapse: collapse;
    width: 100%;
}

/* Mobile: card layout */
@media screen and (max-width: 768px) {
    table {
        border: 0;
    }
    
    thead {
        display: none;
    }
    
    tbody tr {
        display: block;
        margin-bottom: 20px;
        border: 2px solid #ddd;
        border-radius: 8px;
        padding: 10px;
    }
    
    tbody td {
        display: block;
        text-align: right;
        padding: 10px;
        border: none;
    }
    
    tbody td::before {
        content: attr(data-label);
        float: left;
        font-weight: bold;
    }
    
    tbody td[colspan] {
        text-align: center;
    }
}
</style>

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Position</th>
            <th>Department</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td data-label="Name">John Doe</td>
            <td data-label="Position">Developer</td>
            <td data-label="Department">Engineering</td>
        </tr>
    </tbody>
</table>

Best Practices

✅ Do

  • Use colspan/rowspan for logical data grouping
  • Add scope or headers attributes for accessibility
  • Use colgroup for column styling
  • Test with screen readers
  • Ensure mobile responsiveness
  • Use caption for table description
  • Keep structure simple when possible

❌ Don't

  • Overuse colspan/rowspan unnecessarily
  • Nest tables (use CSS instead)
  • Forget accessibility attributes
  • Make tables too complex to understand
  • Use tables for layout
  • Forget mobile users
  • Use empty cells for spacing

Common Pitfalls

1. Miscounting spans

HTML
<!-- ❌ Wrong: Total columns don't match --&gt;
<tr>
    <td colspan="2">A</td>
    <td>B</td>
    <td>C</td> <!-- 4 columns total --&gt;
</tr>
<tr>
    <td>1</td>
    <td>2</td>
    <td>3</td> <!-- Only 3 columns! --&gt;
</tr>

2. Forgetting accessibility

HTML
<!-- ❌ Bad: Complex table without headers attribute --&gt;
<td>Value</td>

<!-- ✅ Good: --&gt;
<td headers="q1 units">Value</td>

What's Next?

Learn about table accessibility and semantic table structure.