HTML Tables

Master the creation of accessible, responsive HTML tables for displaying structured tabular data effectively.

Understanding HTML Tables

HTML tables are designed specifically for displaying tabular data - information that naturally fits into rows and columns, such as spreadsheets, databases, or comparison charts. Tables provide structure and relationships between data points, making complex information easier to understand and navigate.

While tables were once misused for layout purposes, modern HTML uses tables solely for their intended purpose: presenting data with clear relationships between rows and columns. Proper table markup includes semantic elements that enhance accessibility and provide context to screen readers and other assistive technologies.

📊 Key Concept

Tables should only be used for tabular data - information that has logical relationships between rows and columns. For layout purposes, use CSS Grid or Flexbox instead.

Basic Table Structure

A complete HTML table consists of several key elements working together:

<!-- Basic table structure -->
<table>
    <thead>
        <tr>
            <th>Product</th>
            <th>Price</th>
            <th>Stock</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Laptop</td>
            <td>$899</td>
            <td>12</td>
        </tr>
        <tr>
            <td>Mouse</td>
            <td>$25</td>
            <td>45</td>
        </tr>
    </tbody>
</table>
Table Elements Explained:
  • <table> - Main container for the entire table
  • <thead> - Table header section
  • <tbody> - Table body containing data rows
  • <tfoot> - Table footer (optional)
  • <tr> - Table row
  • <th> - Table header cell
  • <td> - Table data cell

Complete Table with All Sections

<table>
    <!-- Table caption for accessibility -->
    <caption>Quarterly Sales Report 2025</caption>
    
    <!-- Table header -->
    <thead>
        <tr>
            <th scope="col">Product Category</th>
            <th scope="col">Q1 Sales</th>
            <th scope="col">Q2 Sales</th>
            <th scope="col">Total</th>
        </tr>
    </thead>
    
    <!-- Table body -->
    <tbody>
        <tr>
            <th scope="row">Electronics</th>
            <td>$125,000</td>
            <td>$142,000</td>
            <td>$267,000</td>
        </tr>
        <tr>
            <th scope="row">Clothing</th>
            <td>$89,000</td>
            <td>$95,000</td>
            <td>$184,000</td>
        </tr>
        <tr>
            <th scope="row">Books</th>
            <td>$45,000</td>
            <td>$52,000</td>
            <td>$97,000</td>
        </tr>
    </tbody>
    
    <!-- Table footer -->
    <tfoot>
        <tr>
            <th scope="row">Grand Total</th>
            <td>$259,000</td>
            <td>$289,000</td>
            <td><strong>$548,000</strong></td>
        </tr>
    </tfoot>
</table>

Cell Spanning

Tables can span cells across multiple rows or columns using colspan and rowspan attributes:

<!-- Table with spanning cells -->
<table>
    <caption>Project Timeline</caption>
    <thead>
        <tr>
            <th>Phase</th>
            <th colspan="3">Timeline (Weeks)</th>
            <th>Resources</th>
        </tr>
        <tr>
            <th> </th>
            <th>Start</th>
            <th>Duration</th>
            <th>End</th>
            <th>Team Size</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th rowspan="2">Planning</th>
            <td>Week 1</td>
            <td>2 weeks</td>
            <td>Week 2</td>
            <td>3 people</td>
        </tr>
        <tr>
            <td colspan="4">Requirements gathering and initial design</td>
        </tr>
    </tbody>
</table>

Table Accessibility

Making tables accessible ensures all users can understand and navigate the data:

♿ Table Accessibility Best Practices

  • Use captions: Describe the table's purpose and content
  • Scope attributes: Define relationships between headers and data
  • Logical structure: Use thead, tbody, and tfoot appropriately
  • Clear headers: Make column and row headers descriptive
  • Consistent formatting: Keep data types consistent within columns
<!-- Highly accessible table -->
<table role="table" aria-labelledby="table-title">
    <caption id="table-title">
        Student Grade Report - Fall 2025 Semester
        <details>
            <summary>Table Description</summary>
            <p>This table shows student names, course grades, and final averages.</p>
        </details>
    </caption>
    
    <thead>
        <tr>
            <th scope="col" id="student">Student Name</th>
            <th scope="col" id="math">Mathematics</th>
            <th scope="col" id="science">Science</th>
            <th scope="col" id="average">Average</th>
        </tr>
    </thead>
    
    <tbody>
        <tr>
            <th scope="row" headers="student">Alice Johnson</th>
            <td headers="student math">92%</td>
            <td headers="student science">88%</td>
            <td headers="student average">90%</td>
        </tr>
    </tbody>
</table>

Responsive Table Design

Modern tables need to work on all screen sizes. Here are several strategies:

/* Basic responsive table */
.table-container {
    overflow-x: auto;
    margin: 1rem 0;
}

table {
    width: 100%;
    border-collapse: collapse;
    min-width: 500px;
}

/* Mobile-first responsive table */
@media (max-width: 768px) {
    .responsive-table {
        font-size: 0.8rem;
    }
    
    .responsive-table th,
    .responsive-table td {
        padding: 0.5rem 0.25rem;
    }
    
    /* Hide less important columns on mobile */
    .hide-mobile {
        display: none;
    }
}

/* Card-style responsive tables */
@media (max-width: 600px) {
    .card-table {
        border: 0;
    }
    
    .card-table thead {
        display: none;
    }
    
    .card-table tr {
        display: block;
        margin-bottom: 1rem;
        border: 1px solid #ddd;
        border-radius: 8px;
        padding: 1rem;
    }
    
    .card-table td {
        display: block;
        text-align: right;
        border: none;
        padding: 0.5rem 0;
    }
    
    .card-table td::before {
        content: attr(data-label);
        float: left;
        font-weight: bold;
    }
}
<!-- Responsive table with data labels -->
<div class="table-container">
    <table class="card-table">
        <thead>
            <tr>
                <th>Product</th>
                <th>Price</th>
                <th class="hide-mobile">Stock</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td data-label="Product:">Laptop</td>
                <td data-label="Price:">$899</td>
                <td data-label="Stock:" class="hide-mobile">12</td>
                <td data-label="Status:">In Stock</td>
            </tr>
        </tbody>
    </table>
</div>

Table Styling

CSS provides extensive control over table appearance:

/* Modern table styling */
table {
    width: 100%;
    border-collapse: collapse;
    margin: 1rem 0;
    font-family: 'Inter', sans-serif;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    border-radius: 8px;
    overflow: hidden;
}

caption {
    font-size: 1.25rem;
    font-weight: 600;
    margin-bottom: 1rem;
    text-align: left;
    color: #2c3e50;
}

th {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    font-weight: 600;
    padding: 1rem;
    text-align: left;
    border-bottom: 2px solid #5a67d8;
}

td {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid #e2e8f0;
    vertical-align: top;
}

/* Alternating row colors */
tbody tr:nth-child(even) {
    background-color: #f8fafc;
}

/* Hover effects */
tbody tr:hover {
    background-color: #e6fffa;
    transform: scale(1.01);
    transition: all 0.2s ease;
}

/* Footer styling */
tfoot th, tfoot td {
    background-color: #f1f5f9;
    font-weight: 600;
    border-top: 2px solid #cbd5e0;
}

Advanced Table Features

Sortable Tables

<!-- Table with sort indicators (CSS only) -->
<table class="sortable">
    <thead>
        <tr>
            <th tabindex="0" role="button">
                Name <span class="sort-indicator"></span>
            </th>
            <th tabindex="0" role="button">
                Age <span class="sort-indicator"></span>
            </th>
        </tr>
    </thead>
</table>

<style>
.sortable th {
    cursor: pointer;
    user-select: none;
}

.sortable th:hover {
    background-color: rgba(255,255,255,0.1);
}

.sort-indicator {
    opacity: 0.5;
    margin-left: 0.5rem;
}

.sortable th:focus {
    outline: 2px solid #fff;
    outline-offset: -2px;
}
</style>

Tables with Rich Content

<!-- Table with images and buttons -->
<table>
    <caption>Product Catalog</caption>
    <thead>
        <tr>
            <th>Image</th>
            <th>Product</th>
            <th>Description</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <img src="laptop-thumb.jpg" 
                     alt="Gaming laptop"
                     class="product-thumb">
            </td>
            <td>
                <h4>Gaming Laptop Pro</h4>
                <span class="price">$1,299</span>
            </td>
            <td>
                <p>High-performance gaming laptop with RTX graphics.</p>
                <ul>
                    <li>16GB RAM</li>
                    <li>1TB SSD</li>
                </ul>
            </td>
            <td>
                <button class="btn-primary">View Details</button>
                <button class="btn-secondary">Add to Cart</button>
            </td>
        </tr>
    </tbody>
</table>

Common Table Mistakes

❌ Mistakes to Avoid:
  • Using tables for layout: Use CSS Grid or Flexbox instead
  • Missing table headers: Always use th elements for headers
  • No scope attributes: Essential for screen reader navigation
  • Missing captions: Tables need descriptive captions
  • Inconsistent data types: Keep columns consistent
  • Non-responsive design: Tables must work on mobile
<!-- ❌ Wrong: Table for layout -->
<table>
    <tr>
        <td>Sidebar content</td>
        <td>Main content</td>
    </tr>
</table>

<!-- ✅ Correct: CSS Grid for layout -->
<div class="grid-layout">
    <aside>Sidebar content</aside>
    <main>Main content</main>
</div>

<style>
.grid-layout {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1rem;
}
</style>