· Tutorial
HTML Tables: When and How to Use Them
Master data tables and learn when tables are the right choice.
HTML tables are designed for one thing: displaying tabular data—information that naturally fits in rows and columns. Despite their reputation from the bad old days of table-based layouts, tables are essential for presenting structured data like schedules, pricing charts, or financial reports. In this guide, you’ll learn when to use tables, how to build them correctly, and how to make them accessible and responsive.
Remember: Tables are for data, not for page layout. That’s what CSS is for!
When to Use Tables
✅ Good Uses for Tables
Tables are perfect for data that has a clear relationship between rows and columns:
- Financial data - Budgets, invoices, balance sheets
- Schedules - Calendars, timetables, class schedules
- Comparisons - Product features, pricing plans
- Statistics - Sports scores, survey results, analytics
- Lists with multiple attributes - Employee directories, product catalogs
❌ Don’t Use Tables For
- Page layout - Use CSS Grid or Flexbox instead
- Navigation menus - Use lists (
<ul>,<li>) - Image galleries - Use CSS Grid
- Form layouts - Use proper form elements and CSS
- Any non-tabular content - If it doesn’t need rows and columns, don’t use a table
Basic Table Structure
Every table needs these core elements:
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget</td>
<td>$19.99</td>
<td>47</td>
</tr>
<tr>
<td>Gadget</td>
<td>$29.99</td>
<td>23</td>
</tr>
</tbody>
</table>Key elements:
<table>- The container<thead>- Table header section<tbody>- Table body section<tr>- Table row<th>- Table header cell (bold by default)<td>- Table data cell
Adding a Caption
Always add a caption to describe what the table shows:
<table>
<caption>Q4 2023 Sales by Region</caption>
<thead>
<tr>
<th>Region</th>
<th>Revenue</th>
<th>Growth</th>
</tr>
</thead>
<tbody>
<tr>
<td>North America</td>
<td>$2.4M</td>
<td>+12%</td>
</tr>
<tr>
<td>Europe</td>
<td>$1.8M</td>
<td>+8%</td>
</tr>
</tbody>
</table>The <caption> element helps screen reader users understand the table before diving into the data.
Table Headers with Scope
Use the scope attribute to explicitly define what each header describes:
<table>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Revenue</th>
<td>$1.2M</td>
<td>$1.5M</td>
<td>$1.8M</td>
<td>$2.1M</td>
</tr>
<tr>
<th scope="row">Expenses</th>
<td>$800K</td>
<td>$850K</td>
<td>$920K</td>
<td>$980K</td>
</tr>
</tbody>
</table>scope="col"- Header describes a columnscope="row"- Header describes a row
This helps screen readers announce “Revenue, Q1: $1.2M” instead of just reading numbers.
Spanning Rows and Columns
Use colspan and rowspan for cells that span multiple columns or rows:
<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">Price</th>
<td>$9/mo</td>
<td>$29/mo</td>
<td>Custom</td>
</tr>
<tr>
<th scope="row">Users</th>
<td>1</td>
<td>10</td>
<td>Unlimited</td>
</tr>
<tr>
<th scope="row">Support</th>
<td colspan="2">Email only</td>
<td>24/7 Phone + Email</td>
</tr>
</tbody>
</table>The colspan="2" makes that cell span two columns.
Adding a Footer
Use <tfoot> for summary rows:
<table>
<caption>Monthly Expenses</caption>
<thead>
<tr>
<th scope="col">Category</th>
<th scope="col">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Rent</th>
<td>$1,200</td>
</tr>
<tr>
<th scope="row">Utilities</th>
<td>$150</td>
</tr>
<tr>
<th scope="row">Internet</th>
<td>$60</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$1,410</td>
</tr>
</tfoot>
</table>Styling Tables with CSS
Basic table styling for readability:
<style>
table {
width: 100%;
border-collapse: collapse;
font-family: system-ui, sans-serif;
}
th, td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background: #f8f9fa;
font-weight: 600;
color: #333;
}
tbody tr:hover {
background: #f8f9fa;
}
caption {
font-weight: bold;
font-size: 1.25rem;
margin-bottom: 0.5rem;
text-align: left;
}
tfoot {
font-weight: bold;
background: #e9ecef;
}
</style>
<table>
<caption>Employee Directory</caption>
<thead>
<tr>
<th>Name</th>
<th>Department</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jane Smith</td>
<td>Engineering</td>
<td>[email protected]</td>
</tr>
<tr>
<td>John Doe</td>
<td>Marketing</td>
<td>[email protected]</td>
</tr>
</tbody>
</table>Responsive Tables
Tables can be challenging on small screens. Here are three approaches:
1. Horizontal Scroll
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
table {
min-width: 600px;
}<div class="table-container">
<table>
<!-- Table content -->
</table>
</div>2. Stackable Rows
@media (max-width: 768px) {
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
display: none;
}
td {
text-align: right;
padding-left: 50%;
position: relative;
}
td:before {
content: attr(data-label);
position: absolute;
left: 0;
padding-left: 15px;
font-weight: bold;
text-align: left;
}
}<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Product">Widget</td>
<td data-label="Price">$19.99</td>
<td data-label="Stock">47</td>
</tr>
</tbody>
</table>3. Side-by-Side Cards
For small tables, convert to cards on mobile:
@media (max-width: 768px) {
table, thead, tbody, tr {
display: block;
}
thead {
display: none;
}
tr {
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
td {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
}
td:before {
content: attr(data-label);
font-weight: bold;
}
}Accessible Tables Checklist
Make your tables accessible to everyone:
- ✅ Use
<caption>to describe the table - ✅ Use
<th>for headers, not<td> - ✅ Add
scopeattribute to headers - ✅ Use
<thead>,<tbody>,<tfoot>to group rows - ✅ Don’t merge cells unless necessary
- ✅ Provide alternative views for complex tables
- ✅ Test with a screen reader
- ✅ Ensure keyboard navigation works
- ✅ Use sufficient color contrast
- ✅ Don’t rely on color alone to convey information
Complete Example
Here’s a fully-featured, accessible table:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accessible Table Example</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 900px;
margin: 2rem auto;
padding: 0 1rem;
}
table {
width: 100%;
border-collapse: collapse;
margin: 2rem 0;
}
caption {
font-size: 1.5rem;
font-weight: bold;
text-align: left;
margin-bottom: 1rem;
color: #333;
}
th, td {
padding: 1rem;
text-align: left;
border: 1px solid #ddd;
}
thead th {
background: #0066cc;
color: white;
font-weight: 600;
}
tbody tr:nth-child(even) {
background: #f8f9fa;
}
tbody tr:hover {
background: #e9ecef;
}
tfoot {
background: #f8f9fa;
font-weight: bold;
}
.price {
text-align: right;
font-family: 'Courier New', monospace;
}
</style>
</head>
<body>
<table>
<caption>Pricing Plans Comparison</caption>
<thead>
<tr>
<th scope="col">Feature</th>
<th scope="col">Starter</th>
<th scope="col">Professional</th>
<th scope="col">Enterprise</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Monthly Price</th>
<td class="price">$9</td>
<td class="price">$29</td>
<td class="price">$99</td>
</tr>
<tr>
<th scope="row">Users</th>
<td>1</td>
<td>5</td>
<td>Unlimited</td>
</tr>
<tr>
<th scope="row">Storage</th>
<td>10 GB</td>
<td>100 GB</td>
<td>1 TB</td>
</tr>
<tr>
<th scope="row">Support</th>
<td>Email</td>
<td>Priority Email</td>
<td>24/7 Phone + Email</td>
</tr>
<tr>
<th scope="row">API Access</th>
<td>❌</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<th scope="row">Custom Domain</th>
<td>❌</td>
<td>✅</td>
<td>✅</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Annual Savings</td>
<td class="price">$0</td>
<td class="price">$49</td>
<td class="price">$189</td>
</tr>
</tfoot>
</table>
</body>
</html>Common Table Mistakes
❌ Using tables for layout - Use CSS Grid/Flexbox ❌ Forgetting <thead> and <tbody> - Improves structure ❌ No caption - Screen readers need context ❌ Using <td> for headers - Always use <th> ❌ No scope attribute - Screen readers struggle ❌ Overly complex tables - Simplify when possible ❌ Not making tables responsive - Test on mobile ❌ Using color only to convey meaning - Bad accessibility
When to Use Lists Instead
If your data doesn’t need both rows AND columns, use a list:
<!-- ❌ Overkill table -->
<table>
<tr><td>Item 1</td></tr>
<tr><td>Item 2</td></tr>
<tr><td>Item 3</td></tr>
</table>
<!-- ✅ Simple list -->
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>Keep Learning
- Semantic HTML Guide - Use the right elements
- Building Accessible Forms - Form accessibility
- Common HTML Mistakes - Avoid table errors
- Explore Templates - See table examples
Try building tables in the htmlEditor.net playground right now!
Remember: Tables are powerful tools for displaying structured data. Use them wisely, make them accessible, and they’ll serve your users well.