HTML Tables: A Complete Guide to Creating and Styling Tables
HTML tables are one of the oldest and most useful elements in web development. They are designed to display structured, tabular data such as pricing plans, schedules, statistics and comparison charts. While tables were once misused for page layouts, modern HTML and CSS have given them a clear purpose. When used correctly, tables are semantic, accessible and easy to style.
HTML Table Basics
Every HTML table starts with the <table> element. Inside the table you define rows with <tr>, header cells with <th> and data cells with <td>. Here is a minimal example:
<table>
<tr>
<th>Name</th>
<th>Role</th>
<th>Status</th>
</tr>
<tr>
<td>Alice</td>
<td>Developer</td>
<td>Active</td>
</tr>
<tr>
<td>Bob</td>
<td>Designer</td>
<td>Inactive</td>
</tr>
</table>
The <th> element is rendered bold and centered by default. It tells browsers and screen readers that the cell is a header, not regular data. Always use <th> for headers instead of styling a <td> to look bold.
Table Head, Body and Footer
For proper semantic structure, wrap your table rows in <thead>, <tbody> and <tfoot>. These elements group rows into logical sections:
<table>
<thead>
<tr><th>Product</th><th>Price</th></tr>
</thead>
<tbody>
<tr><td>Widget</td><td>$9.99</td></tr>
<tr><td>Gadget</td><td>$19.99</td></tr>
</tbody>
<tfoot>
<tr><td>Total</td><td>$29.98</td></tr>
</tfoot>
</table>
The <thead> section stays fixed at the top when printing long tables. The <tfoot> appears at the bottom regardless of where you place it in the markup. Browsers also use these groups to enable independent scrolling of the body while keeping headers visible.
Spanning Rows and Columns
The colspan attribute lets a cell stretch across multiple columns, while rowspan lets it stretch across multiple rows. These are useful for merged headers, grouped categories and summary rows.
<tr>
<th colspan="3">Q1 Sales Report</th>
</tr>
<tr>
<td rowspan="2">North Region</td>
<td>January</td>
<td>$12,000</td>
</tr>
<tr>
<td>February</td>
<td>$15,000</td>
</tr>
When you use colspan="3", the cell occupies three columns, so that row needs three fewer <td> elements. Similarly, rowspan="2" means the cell spans two rows, so the next row should omit one cell to compensate.
Styling Tables with CSS
Modern CSS makes it easy to create clean, professional-looking tables. Start with border-collapse: collapse to merge cell borders into single lines. Then add padding, borders and hover effects:
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px 16px;
border: 1px solid #e5e7eb;
text-align: left;
}
th {
background-color: #f9fafb;
font-weight: 600;
}
/* Striped rows */
tbody tr:nth-child(even) {
background-color: #f3f4f6;
}
/* Hover effect */
tbody tr:hover {
background-color: #e0e7ff;
}
Striped rows improve readability by making it easier to follow data across wide tables. Hover effects provide visual feedback and help users focus on the current row. For dark themes, swap the background colors for darker shades like #1f2937 and #111827.
Responsive Tables
Tables with many columns can overflow on small screens. The simplest fix is to wrap the table in a scrollable container:
<div style="overflow-x: auto;">
<table>...</table>
</div>
For a better mobile experience, you can stack cells vertically using CSS. This converts each row into a card-like block on narrow screens:
@media (max-width: 640px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead { display: none; }
td::before {
content: attr(data-label);
font-weight: 600;
margin-right: 8px;
}
}
With this approach, each <td> needs a data-label attribute containing the column name. Container queries offer another modern solution. You can apply stacking styles based on the container width rather than the viewport, which is more flexible when tables are used inside various layout contexts.
Accessible Tables
Accessibility is essential for tables because screen readers need clear relationships between headers and data cells. Follow these practices to make your tables accessible:
- Add a
<caption>element as the first child of your table. It acts as a visible title and helps screen readers describe the purpose of the table. - Use the
scopeattribute on<th>elements. Setscope="col"for column headers andscope="row"for row headers. - Add
aria-labeloraria-describedbywhen the table purpose is not obvious from context. - Avoid using empty header cells. If a column does not have a visible header, provide one with the
sr-onlyclass so screen readers can still announce it.
<table aria-label="Employee directory">
<caption>List of employees and their departments</caption>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Department</th>
</tr>
</thead>
</table>
Common Mistakes to Avoid
- Missing thead. Without
<thead>and<tbody>, browsers add an implicit tbody. This can break CSS selectors and reduce accessibility. Always include them explicitly. - Inline styles. Avoid putting styles directly on table elements. Use CSS classes instead. Inline styles are harder to maintain, override and make responsive.
- Fixed widths. Setting pixel widths on columns causes overflow on small screens. Use percentages or let columns size naturally with
table-layout: auto. - Nesting tables for layout. Tables should only be used for tabular data, never for page layouts. Use CSS Grid or Flexbox for layout instead. Nested tables also cause major accessibility issues for screen readers.
- Missing scope attributes. Complex tables with both row and column headers need scope attributes so assistive technology can associate each data cell with its correct header.
Generate HTML tables instantly
Define rows, columns, headers and alignment visually. Copy the clean HTML and CSS code with one click.
Open HTML Table Generator