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:
- Price comparisons
- Schedules and timetables
- Statistics and data
- Feature comparison charts
- Spreadsheet-like data
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?
<thead>— The header section. Browsers can keep this visible when scrolling long tables.<tbody>— The main data. Makes styling easier and improves screen reader navigation.<tfoot>— Summary or total rows. Browsers always display this at the bottom, even if you put it before<tbody>in your code.
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
border-collapse: collapse;— Removes gaps between cell borders:nth-child(even)— Creates zebra striping for easier reading:hover— Highlights rows on mouseovertext-align— Aligns cell content (left, center, right)
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!
- Basic: Create a simple 3×3 table with headers and data
- Intermediate: Build a weekly schedule with colspan for lunch breaks
- 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:
- HTML Forms — Collect user input
- Semantic HTML — Structure your pages properly
- Pricing Table Template — Ready-to-use example
- Back to HTML Basics — Review the fundamentals
💡 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.