HTML Tables: Displaying Data in Rows and Columns

Tables are perfect for displaying organized data—schedules, pricing, statistics, comparisons. In this tutorial, you’ll learn to create accessible HTML tables from simple grids to complex data displays. By the end, you’ll know when (and when not!) to use tables.

When to Use Tables

Tables are designed for tabular data—information that naturally fits into rows and columns. Good uses for tables include:

Important: Don’t use tables for page layout! In the past, developers used tables to create multi-column layouts, but this is outdated and creates accessibility problems. Use CSS Grid or Flexbox for layouts instead.

Your First Table

Let’s start with the simplest possible table:

<table>
<tr>
  <td>Row 1, Cell 1</td>
  <td>Row 1, Cell 2</td>
</tr>
<tr>
  <td>Row 2, Cell 1</td>
  <td>Row 2, Cell 2</td>
</tr>
</table>

Understanding the Elements

<table> — The container for your entire table

<tr> — Table Row. Each <tr> creates a new horizontal row.

<td> — Table Data (cell). Contains the actual content in each cell.

Adding Table Headers

Headers describe what each column (or row) contains. Use <th> instead of <td> for header cells:

<table>
<tr>
  <th>Name</th>
  <th>Age</th>
  <th>City</th>
</tr>
<tr>
  <td>Alice</td>
  <td>28</td>
  <td>New York</td>
</tr>
<tr>
  <td>Bob</td>
  <td>34</td>
  <td>Los Angeles</td>
</tr>
<tr>
  <td>Carol</td>
  <td>25</td>
  <td>Chicago</td>
</tr>
</table>

Headers are automatically bold and centered. More importantly, screen readers announce them to help users understand the data.

Table Structure: thead, tbody, tfoot

For better organization and accessibility, wrap your table sections in <thead>, <tbody>, and optionally <tfoot>:

<table>
<thead>
  <tr>
    <th>Product</th>
    <th>Price</th>
    <th>Quantity</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>Apples</td>
    <td>$1.50</td>
    <td>10</td>
  </tr>
  <tr>
    <td>Oranges</td>
    <td>$2.00</td>
    <td>8</td>
  </tr>
  <tr>
    <td>Bananas</td>
    <td>$0.75</td>
    <td>15</td>
  </tr>
</tbody>
<tfoot>
  <tr>
    <td>Total</td>
    <td>$4.25</td>
    <td>33</td>
  </tr>
</tfoot>
</table>

Why Use These Elements?

Adding a Caption

The <caption> element provides a title for your table. It must be the first child inside <table>:

<table>
<caption>Monthly Sales Report - Q1 2024</caption>
<thead>
  <tr>
    <th>Month</th>
    <th>Sales</th>
    <th>Target</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>January</td>
    <td>$12,500</td>
    <td>$10,000</td>
  </tr>
  <tr>
    <td>February</td>
    <td>$15,200</td>
    <td>$12,000</td>
  </tr>
  <tr>
    <td>March</td>
    <td>$18,750</td>
    <td>$15,000</td>
  </tr>
</tbody>
</table>

Captions are essential for accessibility—they tell screen reader users what the table is about before they dive into the data.

Spanning Rows and Columns

Sometimes you need a cell to span multiple columns or rows. Use colspan and rowspan attributes:

Colspan (Spanning Columns)

<table>
<tr>
  <th colspan="3">Student Grades</th>
</tr>
<tr>
  <th>Name</th>
  <th>Math</th>
  <th>English</th>
</tr>
<tr>
  <td>Alice</td>
  <td>95</td>
  <td>88</td>
</tr>
<tr>
  <td>Bob</td>
  <td>82</td>
  <td>91</td>
</tr>
</table>

Rowspan (Spanning Rows)

<table>
<tr>
  <th>Day</th>
  <th>Time</th>
  <th>Class</th>
</tr>
<tr>
  <td rowspan="2">Monday</td>
  <td>9:00 AM</td>
  <td>Math</td>
</tr>
<tr>
  <td>11:00 AM</td>
  <td>English</td>
</tr>
<tr>
  <td rowspan="2">Tuesday</td>
  <td>9:00 AM</td>
  <td>Science</td>
</tr>
<tr>
  <td>11:00 AM</td>
  <td>History</td>
</tr>
</table>

Making Tables Accessible

Tables can be difficult for screen reader users to understand. Follow these practices:

1. Use the scope Attribute

Tell screen readers whether a header applies to a row or column:

<table>
<tr>
  <th scope="col">Name</th>
  <th scope="col">Email</th>
  <th scope="col">Role</th>
</tr>
<tr>
  <th scope="row">Alice</th>
  <td>[email protected]</td>
  <td>Developer</td>
</tr>
<tr>
  <th scope="row">Bob</th>
  <td>[email protected]</td>
  <td>Designer</td>
</tr>
</table>

2. Always Include a Caption

The caption gives context before users start navigating the table.

3. Use Simple Structures When Possible

Complex tables with lots of colspan/rowspan are harder for everyone to understand. Break them into smaller, simpler tables if possible.

Styling Tables with CSS

HTML tables look basic by default. Here’s how to make them look professional:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styled Table</title>
<style>
  table {
    width: 100%;
    border-collapse: collapse;
    font-family: Arial, sans-serif;
    margin: 20px 0;
  }

  caption {
    font-size: 1.5rem;
    font-weight: bold;
    margin-bottom: 15px;
    text-align: left;
  }

  th, td {
    padding: 12px 15px;
    text-align: left;
    border-bottom: 1px solid #ddd;
  }

  th {
    background-color: #2563eb;
    color: white;
    font-weight: 600;
  }

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

  tbody tr:nth-child(even) {
    background-color: #f9fafb;
  }

  tfoot {
    font-weight: bold;
    background-color: #f3f4f6;
  }
</style>
</head>
<body>
<table>
  <caption>Team Sales Performance - Q4 2024</caption>
  <thead>
    <tr>
      <th scope="col">Salesperson</th>
      <th scope="col">Region</th>
      <th scope="col">Sales</th>
      <th scope="col">Target</th>
      <th scope="col">Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice Johnson</td>
      <td>North</td>
      <td>$125,000</td>
      <td>$100,000</td>
      <td>✓ Exceeded</td>
    </tr>
    <tr>
      <td>Bob Smith</td>
      <td>South</td>
      <td>$98,500</td>
      <td>$100,000</td>
      <td>Near Target</td>
    </tr>
    <tr>
      <td>Carol Williams</td>
      <td>East</td>
      <td>$142,300</td>
      <td>$120,000</td>
      <td>✓ Exceeded</td>
    </tr>
    <tr>
      <td>David Brown</td>
      <td>West</td>
      <td>$87,200</td>
      <td>$90,000</td>
      <td>Near Target</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td colspan="2">Total</td>
      <td>$453,000</td>
      <td>$410,000</td>
      <td>✓ Team Goal Met</td>
    </tr>
  </tfoot>
</table>
</body>
</html>

Key CSS Properties for Tables

Responsive Tables

Tables can be tricky on mobile devices. Here’s a simple solution—make them horizontally scrollable:

<style>
.table-container {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

table {
  min-width: 600px; /* Prevents squishing */
}
</style>

<div class="table-container">
<table>
  <!-- Your table here -->
</table>
</div>

Complete Example: Pricing Table

Let’s build a real-world pricing comparison table:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pricing Plans</title>
<style>
  body {
    font-family: system-ui, sans-serif;
    max-width: 900px;
    margin: 40px auto;
    padding: 20px;
  }

  h1 {
    text-align: center;
    margin-bottom: 30px;
  }

  .table-container {
    overflow-x: auto;
  }

  table {
    width: 100%;
    border-collapse: collapse;
    text-align: center;
  }

  th, td {
    padding: 15px;
    border: 1px solid #e5e7eb;
  }

  thead th {
    background: #1f2937;
    color: white;
    font-size: 1.25rem;
  }

  thead th.popular {
    background: #2563eb;
    position: relative;
  }

  thead th.popular::before {
    content: 'Most Popular';
    position: absolute;
    top: -25px;
    left: 50%;
    transform: translateX(-50%);
    background: #fbbf24;
    color: #1f2937;
    padding: 4px 12px;
    border-radius: 12px;
    font-size: 0.75rem;
    font-weight: bold;
  }

  .price {
    font-size: 2rem;
    font-weight: bold;
    color: #1f2937;
  }

  .price span {
    font-size: 1rem;
    color: #6b7280;
  }

  tbody tr:nth-child(odd) {
    background: #f9fafb;
  }

  .check {
    color: #10b981;
    font-size: 1.25rem;
  }

  .cross {
    color: #ef4444;
    font-size: 1.25rem;
  }

  tfoot td {
    padding: 20px;
  }

  button {
    padding: 12px 30px;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    cursor: pointer;
  }

  .btn-outline {
    background: white;
    border: 2px solid #2563eb;
    color: #2563eb;
  }

  .btn-primary {
    background: #2563eb;
    color: white;
  }
</style>
</head>
<body>
<h1>Choose Your Plan</h1>

<div class="table-container">
  <table>
    <thead>
      <tr>
        <th>Features</th>
        <th>Basic</th>
        <th class="popular">Pro</th>
        <th>Enterprise</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>Price</strong></td>
        <td class="price">$9<span>/month</span></td>
        <td class="price">$29<span>/month</span></td>
        <td class="price">$99<span>/month</span></td>
      </tr>
      <tr>
        <td>Projects</td>
        <td>3</td>
        <td>Unlimited</td>
        <td>Unlimited</td>
      </tr>
      <tr>
        <td>Storage</td>
        <td>5 GB</td>
        <td>50 GB</td>
        <td>500 GB</td>
      </tr>
      <tr>
        <td>Team Members</td>
        <td>1</td>
        <td>10</td>
        <td>Unlimited</td>
      </tr>
      <tr>
        <td>Priority Support</td>
        <td class="cross">✕</td>
        <td class="check">✓</td>
        <td class="check">✓</td>
      </tr>
      <tr>
        <td>Custom Domain</td>
        <td class="cross">✕</td>
        <td class="check">✓</td>
        <td class="check">✓</td>
      </tr>
      <tr>
        <td>Analytics</td>
        <td class="cross">✕</td>
        <td class="cross">✕</td>
        <td class="check">✓</td>
      </tr>
    </tbody>
    <tfoot>
      <tr>
        <td></td>
        <td><button class="btn-outline">Get Started</button></td>
        <td><button class="btn-primary">Get Started</button></td>
        <td><button class="btn-outline">Contact Sales</button></td>
      </tr>
    </tfoot>
  </table>
</div>
</body>
</html>

Common Mistakes to Avoid

Avoid Using Tables for Layout

<!-- Bad: table for page layout -->
<table>
<tr>
  <td>Sidebar</td>
  <td>Main Content</td>
</tr>
</table>

<!-- Good: CSS for layout -->
<div style="display: flex;">
<aside>Sidebar</aside>
<main>Main Content</main>
</div>

Avoid Missing Headers

<!-- Bad: no headers -->
<table>
<tr>
  <td>Alice</td>
  <td>28</td>
</tr>
</table>

<!-- Good: headers included -->
<table>
<tr>
  <th>Name</th>
  <th>Age</th>
</tr>
<tr>
  <td>Alice</td>
  <td>28</td>
</tr>
</table>

Do Not Forget Border-Collapse

<!-- Without border-collapse, you get double borders -->
<style>
/* Good practice */
table {
  border-collapse: collapse;
}
</style>

Try It Yourself!

  1. Basic: Create a simple 3×3 table with headers and data
  2. Intermediate: Build a weekly schedule with colspan for lunch breaks
  3. Advanced: Create a responsive product comparison table with styling

Next Steps

Excellent work! You now know how to create and style HTML tables. Continue learning:

💡 Practice Makes Perfect

Copy the pricing table example and customize it in our free online HTML editor! Try changing colors, adding more features, or creating your own comparison table.