HTML tables are one of the most useful — and most misunderstood — elements in the language. Used correctly, they present tabular data clearly and accessibly. Used incorrectly (for page layout, for example), they cause maintenance headaches and accessibility failures. This guide covers everything from basic table structure to merging cells with colspan and rowspan. Use our HTML table generator to build tables visually while following along.
Basic Table Structure
An HTML table is built from four core elements:
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alex</td>
<td>Developer</td>
<td>London</td>
</tr>
</tbody>
</table>
<table>— the container element<thead>— groups the header row(s)<tbody>— groups the data rows<tr>— a table row<th>— a header cell (bold and centred by default; announced to screen readers as a header)<td>— a data cell
Always separate <thead> from <tbody>. It allows browsers to scroll the body independently of the header on long tables, enables proper printing (headers repeat on each printed page), and makes the structure semantically meaningful to assistive technologies.
Colspan: Merging Columns
The colspan attribute makes a cell span across multiple columns. When you set colspan="2", remove the adjacent cell that it covers — the total cell count per row must remain consistent:
<tr>
<th colspan="2">Full Name</th>
<th>Location</th>
</tr>
<tr>
<td>Alex</td>
<td>Johnson</td>
<td>London</td>
</tr>
Rowspan: Merging Rows
rowspan makes a cell span multiple rows. The cell appears in the first row; subsequent rows omit the cell it covers:
<tr>
<td rowspan="2">EU Region</td>
<td>Germany</td>
</tr>
<tr>
<td>France</td>
</tr>
Styling HTML Tables with CSS
By default, tables have separated borders. Use border-collapse: collapse to merge adjacent cell borders into a single border:
table {
border-collapse: collapse;
width: 100%;
}
th, td {
padding: 10px 14px;
border: 1px solid #e2e8f0;
text-align: left;
}
thead th {
background: #1e3a8a;
color: white;
}
tbody tr:nth-child(even) {
background: #f8fafc;
}
Accessibility: scope and caption
Add a <caption> element immediately after <table> to give the table a visible title. Screen readers announce it before reading the table content. Add scope="col" to header cells to explicitly associate them with their column:
<table>
<caption>Q1 Sales by Region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Sales</th>
</tr>
</thead>
...
</table>
Making Tables Responsive
Wide tables overflow their containers on small screens. Wrap them in a scroll container:
<div style="overflow-x: auto;">
<table>...</table>
</div>
This allows the table to scroll horizontally on mobile without breaking the page layout. After building your table structure, run it through the HTML validator to confirm the cell counts are consistent across all rows — mismatched cell counts are the most common table error and cause unpredictable rendering across browsers.
When NOT to Use Tables
Tables are for tabular data — information where rows and columns have meaning. Never use tables for page layout. CSS Grid and Flexbox have made table-based layouts completely unnecessary, and table layouts are a significant accessibility problem: screen readers read table cells in DOM order, which may not match the visual reading order your layout implies.