2.5: Tables & Data Presentation

Structure tabular data with HTML tables using proper semantic elements for headers, rows, and cells. Understand when to use tables and how to make them accessible.

1. Basic Table Structure

Simple Table

<table>
  <tr>
    <th>Name</th>
    <th>Age</th>
    <th>City</th>
  </tr>
  <tr>
    <td>John Doe</td>
    <td>28</td>
    <td>New York</td>
  </tr>
  <tr>
    <td>Jane Smith</td>
    <td>34</td>
    <td>Los Angeles</td>
  </tr>
</table>

Elements:

  • <table> - Container for the table
  • <tr> - Table Row
  • <th> - Table Header cell (bold, centered by default)
  • <td> - Table Data cell (regular text)

Rendered:

NameAgeCity
John Doe28New York
Jane Smith34Los Angeles

2. Table Sections (thead, tbody, tfoot)

Proper semantic structure with header, body, and footer sections:

<table>
  <thead>
    <tr>
      <th>Product</th>
      <th>Price</th>
      <th>Quantity</th>
      <th>Total</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Laptop</td>
      <td>$999</td>
      <td>2</td>
      <td>$1,998</td>
    </tr>
    <tr>
      <td>Mouse</td>
      <td>$25</td>
      <td>5</td>
      <td>$125</td>
    </tr>
    <tr>
      <td>Keyboard</td>
      <td>$75</td>
      <td>3</td>
      <td>$225</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td colspan="3">Grand Total</td>
      <td>$2,348</td>
    </tr>
  </tfoot>
</table>

Benefits of sections:

  • Better semantic structure
  • Screen readers can navigate by sections
  • Can style sections differently
  • Header/footer can remain visible when scrolling (with CSS)

Element order: <thead>, <tbody>, <tfoot> (browser will render <tfoot> at bottom regardless)


3. Table Caption

Use <caption> to provide a title for the table:

<table>
  <caption>Monthly Sales Report - Q4 2024</caption>
  <thead>
    <tr>
      <th>Month</th>
      <th>Revenue</th>
      <th>Expenses</th>
      <th>Profit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>October</td>
      <td>$50,000</td>
      <td>$30,000</td>
      <td>$20,000</td>
    </tr>
    <tr>
      <td>November</td>
      <td>$65,000</td>
      <td>$35,000</td>
      <td>$30,000</td>
    </tr>
    <tr>
      <td>December</td>
      <td>$80,000</td>
      <td>$40,000</td>
      <td>$40,000</td>
    </tr>
  </tbody>
</table>

Caption placement:

  • Must be first child of <table>
  • Displayed above table by default
  • Can be positioned below with CSS: caption-side: bottom;

4. Column and Row Spanning

Colspan (Spanning Columns)

Merge cells horizontally:

<table>
  <tr>
    <th colspan="3">Personal Information</th>
  </tr>
  <tr>
    <th>First Name</th>
    <th>Last Name</th>
    <th>Email</th>
  </tr>
  <tr>
    <td>John</td>
    <td>Doe</td>
    <td>john@example.com</td>
  </tr>
</table>

Result:

| Personal Information (spans 3 columns) | |---|---|---| | First Name | Last Name | Email | | John | Doe | john@example.com |

Rowspan (Spanning Rows)

Merge cells vertically:

<table>
  <tr>
    <th rowspan="2">Name</th>
    <th colspan="2">Contact</th>
  </tr>
  <tr>
    <th>Email</th>
    <th>Phone</th>
  </tr>
  <tr>
    <td>John Doe</td>
    <td>john@example.com</td>
    <td>555-1234</td>
  </tr>
</table>

Complex Example:

<table>
  <caption>Class Schedule</caption>
  <thead>
    <tr>
      <th>Time</th>
      <th>Monday</th>
      <th>Tuesday</th>
      <th>Wednesday</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>9:00 AM</td>
      <td rowspan="2">Math<br>(Double Period)</td>
      <td>English</td>
      <td>Science</td>
    </tr>
    <tr>
      <td>10:00 AM</td>
      
      <td>History</td>
      <td>Art</td>
    </tr>
    <tr>
      <td>11:00 AM</td>
      <td colspan="3" style="text-align: center;">Lunch Break</td>
    </tr>
  </tbody>
</table>

5. Accessible Tables

Scope Attribute

Define whether header applies to row or column:

<table>
  <thead>
    <tr>
      <th scope="col">Product</th>
      <th scope="col">Price</th>
      <th scope="col">Stock</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Laptop</th>
      <td>$999</td>
      <td>15</td>
    </tr>
    <tr>
      <th scope="row">Mouse</th>
      <td>$25</td>
      <td>50</td>
    </tr>
  </tbody>
</table>

Scope values:

  • scope="col" - Header for column
  • scope="row" - Header for row
  • scope="colgroup" - Header for group of columns
  • scope="rowgroup" - Header for group of rows

Headers Attribute (Complex Tables)

For complex tables with multiple header levels:

<table>
  <tr>
    <th id="name">Name</th>
    <th id="math">Math</th>
    <th id="science">Science</th>
  </tr>
  <tr>
    <td headers="name">Alice</td>
    <td headers="name math">95</td>
    <td headers="name science">88</td>
  </tr>
  <tr>
    <td headers="name">Bob</td>
    <td headers="name math">82</td>
    <td headers="name science">91</td>
  </tr>
</table>

When to use:

  • Simple tables → Use scope attribute
  • Complex tables → Use headers attribute with IDs

Summary for Screen Readers

<table>
  <caption>
    Student Grades - Fall 2024
    <details>
      <summary>Table Description</summary>
      <p>This table shows final grades for students in Math and Science courses.</p>
    </details>
  </caption>
  
</table>

6. Styling Tables

Basic Table Styling

<table class="styled-table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Role</th>
      <th>Department</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>John Doe</td>
      <td>Developer</td>
      <td>Engineering</td>
    </tr>
    <tr>
      <td>Jane Smith</td>
      <td>Designer</td>
      <td>Creative</td>
    </tr>
  </tbody>
</table>

<style>
.styled-table {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0;
  font-size: 0.9rem;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}

.styled-table thead tr {
  background-color: #0066cc;
  color: white;
  text-align: left;
}

.styled-table th,
.styled-table td {
  padding: 12px 15px;
}

.styled-table tbody tr {
  border-bottom: 1px solid #ddd;
}

.styled-table tbody tr:hover {
  background-color: #f5f5f5;
}

.styled-table tbody tr:last-of-type {
  border-bottom: 2px solid #0066cc;
}
</style>

Striped Rows

.striped-table tbody tr:nth-child(odd) {
  background-color: #f9f9f9;
}

.striped-table tbody tr:nth-child(even) {
  background-color: white;
}

Bordered Table

.bordered-table {
  border-collapse: collapse;
}

.bordered-table th,
.bordered-table td {
  border: 1px solid #ddd;
  padding: 8px 12px;
}

.bordered-table th {
  background-color: #f0f0f0;
}

Sticky Header (Scrollable)

.scrollable-table-container {
  max-height: 400px;
  overflow-y: auto;
  position: relative;
}

.scrollable-table thead th {
  position: sticky;
  top: 0;
  background-color: #0066cc;
  color: white;
  z-index: 10;
}

7. Responsive Tables

Problem: Tables Overflow on Mobile

Tables with many columns don't fit on small screens

Solution 1: Horizontal Scroll

<div class="table-wrapper">
  <table>
    
  </table>
</div>

<style>
.table-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
}

.table-wrapper table {
  min-width: 600px; /* Prevent compression */
}
</style>

Solution 2: Stacked Layout (Mobile)

<table class="responsive-table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Phone</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td data-label="Name">John Doe</td>
      <td data-label="Email">john@example.com</td>
      <td data-label="Phone">555-1234</td>
    </tr>
    <tr>
      <td data-label="Name">Jane Smith</td>
      <td data-label="Email">jane@example.com</td>
      <td data-label="Phone">555-5678</td>
    </tr>
  </tbody>
</table>

<style>
/* Desktop: Normal table */
@media (min-width: 768px) {
  .responsive-table {
    width: 100%;
    border-collapse: collapse;
  }

  .responsive-table th,
  .responsive-table td {
    padding: 12px;
    border: 1px solid #ddd;
  }
}

/* Mobile: Stacked layout */
@media (max-width: 767px) {
  .responsive-table thead {
    display: none; /* Hide header */
  }

  .responsive-table,
  .responsive-table tbody,
  .responsive-table tr,
  .responsive-table td {
    display: block;
    width: 100%;
  }

  .responsive-table tr {
    margin-bottom: 1rem;
    border: 1px solid #ddd;
  }

  .responsive-table td {
    text-align: right;
    padding: 10px;
    border-bottom: 1px solid #eee;
  }

  .responsive-table td::before {
    content: attr(data-label);
    float: left;
    font-weight: bold;
  }

  .responsive-table td:last-child {
    border-bottom: 0;
  }
}
</style>

Solution 3: Card Layout (Mobile)

@media (max-width: 767px) {
  .card-table {
    display: block;
  }

  .card-table thead {
    display: none;
  }

  .card-table tbody,
  .card-table tr {
    display: block;
  }

  .card-table tr {
    background: white;
    border: 1px solid #ddd;
    border-radius: 8px;
    margin-bottom: 1rem;
    padding: 1rem;
  }

  .card-table td {
    display: block;
    margin-bottom: 0.5rem;
  }

  .card-table td::before {
    content: attr(data-label) ": ";
    font-weight: bold;
  }
}

8. When to Use Tables

✅ Use Tables For:

Tabular data:


<table>
  <caption>Product Comparison</caption>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Basic</th>
      <th>Pro</th>
      <th>Enterprise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Storage</th>
      <td>10 GB</td>
      <td>100 GB</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <th scope="row">Users</th>
      <td>1</td>
      <td>5</td>
      <td>Unlimited</td>
    </tr>
  </tbody>
</table>

Data that benefits from comparison:

  • Pricing tables
  • Product specifications
  • Statistical data
  • Schedules and calendars
  • Financial reports

❌ Don't Use Tables For:

Page layout:


<table>
  <tr>
    <td></td>
    <td></td>
  </tr>
</table>

<div class="layout">
  <aside></aside>
  <main></main>
</div>

Non-tabular content:

  • Navigation menus (use <nav> and lists)
  • Image galleries (use CSS Grid/Flexbox)
  • Form layouts (use proper form elements)
  • Page structure (use semantic HTML + CSS)

9. Complete Examples

Financial Report Table

<table class="financial-report">
  <caption>
    <strong>Quarterly Financial Report</strong>
    <span>Q4 2024 - All figures in USD</span>
  </caption>
  <thead>
    <tr>
      <th scope="col">Category</th>
      <th scope="col">Q3 2024</th>
      <th scope="col">Q4 2024</th>
      <th scope="col">Change</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Revenue</th>
      <td>$2,450,000</td>
      <td>$2,890,000</td>
      <td class="positive">+18%</td>
    </tr>
    <tr>
      <th scope="row">Expenses</th>
      <td>$1,800,000</td>
      <td>$1,950,000</td>
      <td class="negative">+8.3%</td>
    </tr>
    <tr>
      <th scope="row">Operating Income</th>
      <td>$650,000</td>
      <td>$940,000</td>
      <td class="positive">+44.6%</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row">Net Profit</th>
      <td>$455,000</td>
      <td>$658,000</td>
      <td class="positive">+44.6%</td>
    </tr>
  </tfoot>
</table>

<style>
.financial-report {
  width: 100%;
  border-collapse: collapse;
  margin: 2rem 0;
  font-family: 'Arial', sans-serif;
}

.financial-report caption {
  padding: 1rem;
  background: #f0f0f0;
  border: 1px solid #ddd;
  border-bottom: none;
  text-align: left;
}

.financial-report caption strong {
  display: block;
  font-size: 1.2rem;
  margin-bottom: 0.5rem;
}

.financial-report caption span {
  color: #666;
  font-size: 0.9rem;
}

.financial-report th,
.financial-report td {
  padding: 12px;
  text-align: left;
  border: 1px solid #ddd;
}

.financial-report thead th {
  background: #2c3e50;
  color: white;
  font-weight: bold;
}

.financial-report tbody th {
  background: #ecf0f1;
  font-weight: 600;
}

.financial-report tfoot th {
  background: #34495e;
  color: white;
  font-weight: bold;
}

.financial-report tfoot td {
  font-weight: bold;
}

.positive {
  color: #27ae60;
  font-weight: bold;
}

.negative {
  color: #e74c3c;
  font-weight: bold;
}
</style>

Pricing Table

<table class="pricing-table">
  <caption>Choose Your Plan</caption>
  <thead>
    <tr>
      <th scope="col">Feature</th>
      <th scope="col">Free</th>
      <th scope="col">Pro</th>
      <th scope="col">Enterprise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Monthly Price</th>
      <td>$0</td>
      <td><strong>$29</strong></td>
      <td>Custom</td>
    </tr>
    <tr>
      <th scope="row">Storage</th>
      <td>5 GB</td>
      <td>100 GB</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <th scope="row">Users</th>
      <td>1</td>
      <td>Up to 10</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <th scope="row">Support</th>
      <td>Community</td>
      <td>Email</td>
      <td>24/7 Phone</td>
    </tr>
    <tr>
      <th scope="row">API Access</th>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td></td>
      <td><a href="/signup?plan=free" class="btn">Sign Up</a></td>
      <td><a href="/signup?plan=pro" class="btn btn-primary">Get Pro</a></td>
      <td><a href="/contact" class="btn">Contact Sales</a></td>
    </tr>
  </tfoot>
</table>

<style>
.pricing-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  margin: 2rem 0;
}

.pricing-table caption {
  font-size: 1.5rem;
  font-weight: bold;
  margin-bottom: 1rem;
}

.pricing-table th,
.pricing-table td {
  padding: 1rem;
  text-align: center;
  border: 1px solid #ddd;
}

.pricing-table thead th {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  font-size: 1.1rem;
}

.pricing-table tbody th {
  text-align: left;
  background: #f8f9fa;
  font-weight: 600;
}

.pricing-table tbody tr:hover {
  background: #f0f0f0;
}

.pricing-table tfoot td {
  background: white;
  border-top: 2px solid #667eea;
}

.btn {
  display: inline-block;
  padding: 0.5rem 1.5rem;
  background: #e0e0e0;
  color: #333;
  text-decoration: none;
  border-radius: 4px;
  font-weight: bold;
}

.btn-primary {
  background: #667eea;
  color: white;
}
</style>

10. Practical Exercises

Exercise 2.5.1: Basic Data Table

Create a table showing:

  1. List of 5 programming languages
  2. Columns: Name, Year Created, Type, Popularity
  3. Proper <thead>, <tbody>, <tfoot> sections
  4. Caption describing the table
  5. Use scope attributes for accessibility

Exercise 2.5.2: Complex Table with Spanning

Build a class schedule table with:

  1. Time slots in first column
  2. Days of week across top
  3. Use rowspan for double-period classes
  4. Use colspan for lunch break across all days
  5. Proper semantic structure

Exercise 2.5.3: Styled Pricing Table

Create a pricing comparison table with:

  1. Three pricing tiers (columns)
  2. At least 6 features (rows)
  3. Styled header with gradient or color
  4. Hover effects on rows
  5. Call-to-action buttons in footer

Exercise 2.5.4: Responsive Table

Build a responsive employee directory:

  1. Columns: Name, Email, Department, Phone, Status
  2. At least 8 rows of data
  3. Horizontal scroll on tablets
  4. Stacked card layout on mobile (using data-label technique)
  5. Test responsiveness at different breakpoints

11. Knowledge Check

Question 1: What's the difference between <th> and <td>?

Show answer `` is for table header cells (bold and centered by default, semantically indicates header). `` is for regular table data cells. Use `` for column/row headers to improve accessibility.

Question 2: Why use <thead>, <tbody>, and <tfoot>?

Show answer For semantic structure and accessibility. These sections help screen readers navigate tables, allow different styling, and enable features like sticky headers. Browser renders `` at bottom regardless of code order.

Question 3: What does the scope attribute do?

Show answer `scope` defines whether a header cell applies to a row (`scope="row"`), column (`scope="col"`), or group. This helps screen readers associate data cells with their headers for better accessibility.

Question 4: How does colspan="3" work?

Show answer It makes a cell span across 3 columns horizontally, merging those cells into one. You must reduce the number of cells in that row by 2 (since one cell now takes the space of three).

Question 5: When should you NOT use tables?

Show answer Don't use tables for page layout, navigation menus, image galleries, or any non-tabular content. Use CSS Grid/Flexbox for layouts. Tables are only for actual tabular data.

Question 6: What's the best way to make tables responsive on mobile?

Show answer Multiple approaches: 1) Horizontal scroll wrapper, 2) Stacked/card layout using CSS media queries and `data-label` attributes, 3) Hide less important columns on mobile. Choose based on data complexity and user needs.

12. Common Mistakes

Using Tables for Layout


<table>
  <tr>
    <td width="200">Sidebar</td>
    <td>Main content</td>
  </tr>
</table>

<div class="layout">
  <aside>Sidebar</aside>
  <main>Main content</main>
</div>

Missing Semantic Structure


<table>
  <tr><th>Name</th><th>Age</th></tr>
  <tr><td>John</td><td>28</td></tr>
</table>

<table>
  <caption>User List</caption>
  <thead>
    <tr><th>Name</th><th>Age</th></tr>
  </thead>
  <tbody>
    <tr><td>John</td><td>28</td></tr>
  </tbody>
</table>

Forgetting Accessibility


<table>
  <tr>
    <th>Product</th>
    <th>Price</th>
  </tr>
  
</table>

<table>
  <tr>
    <th scope="col">Product</th>
    <th scope="col">Price</th>
  </tr>
  
</table>

Not Handling Mobile


<table style="width: 1200px;">
  
</table>

<div style="overflow-x: auto;">
  <table>
    
  </table>
</div>

13. Key Takeaways

  • Use tables for tabular data only - Not for page layout
  • Structure with <thead>, <tbody>, <tfoot> - Better semantics and accessibility
  • Always add <caption> - Describes table purpose
  • Use scope attribute - Helps screen readers (scope="col" or scope="row")
  • colspan and rowspan - Merge cells horizontally and vertically
  • Style with CSS - border-collapse, hover effects, striped rows
  • Make responsive - Horizontal scroll, stacked layout, or hide columns on mobile
  • First row/column often headers - Use <th> instead of <td>
  • Test accessibility - Use screen reader or browser DevTools
  • Consider alternatives - CSS Grid for complex layouts, not tables

14. Further Resources

Official Documentation:

Accessibility:

Responsive Tables:


Next Steps

Excellent! You now know how to create accessible, responsive HTML tables for presenting data.

In Lesson 2.6: Forms & Input Elements, you'll learn how to build interactive forms for collecting user input, including text fields, checkboxes, radio buttons, and form validation.