Some information is just naturally a grid: a price list, a class schedule, a comparison of plans, the stats on a sports page. When your data has rows and columns that line up, an HTML table is the right tool for the job. Tables have a slightly fussier structure than other elements, but once you see how the pieces fit together, they’re completely logical.
This article shows you how to build a table properly, what each element is for, and — importantly — the one big mistake to avoid: using tables for the wrong job.
The pieces of a table
A table is built from a few elements that nest inside one another. Here are the core ones:
<table>— wraps the whole thing.<tr>— a table row (one horizontal row).<th>— a table header cell (the bold titles at the top of columns).<td>— a table data cell (a normal cell with content).
The mental model is simple: a table is a stack of rows (<tr>), and each row is a series of cells (<th> for headers, <td> for data). You build a table row by row, left to right.
Building your first table
Let’s make a small table — a list of three programming languages and what they’re for:
<table>
<tr>
<th>Language</th>
<th>Used for</th>
</tr>
<tr>
<td>HTML</td>
<td>Structure</td>
</tr>
<tr>
<td>CSS</td>
<td>Styling</td>
</tr>
<tr>
<td>JavaScript</td>
<td>Behavior</td>
</tr>
</table>
Read it from the top down and it makes sense:
- The whole thing is wrapped in
<table>. - The first
<tr>is the header row — its cells are<th>, which browsers render bold and centered by default. - Each following
<tr>is a data row, with<td>cells holding the actual content.
Every row has the same number of cells (here, two), which is what keeps the columns lined up. That’s the golden rule of tables: each row should have the same number of cells.
th versus td — it's about meaning, not just bold
Use <th> for header cells and <td> for data cells. Yes, <th> looks bold by default, but the real reason to use it is meaning: it tells screen readers “this cell is a heading for the column (or row) below it.” When a screen-reader user moves through the table, the browser can announce the relevant header for each cell, so they don’t lose track of which column they’re in. Using <th> properly makes your table understandable to everyone.
Giving the table structure: thead, tbody, and a caption
For anything bigger than a tiny table, it’s good practice to group the rows into a head and a body, and to give the table a caption. These small additions buy you clarity and accessibility:
<table>
<caption>Programming languages and their roles</caption>
<thead>
<tr>
<th>Language</th>
<th>Used for</th>
</tr>
</thead>
<tbody>
<tr>
<td>HTML</td>
<td>Structure</td>
</tr>
<tr>
<td>CSS</td>
<td>Styling</td>
</tr>
</tbody>
</table>
A few new pieces here:
<caption>is a title for the whole table. It goes right after the opening<table>tag and describes what the table contains — helpful for everyone, and especially for screen-reader users.<thead>groups the header row(s).<tbody>groups the data rows.
These grouping elements don’t change much visually on their own, but they make the table’s structure explicit, which helps accessibility and makes styling easier later.
Cells that span multiple columns or rows
Sometimes a single cell needs to stretch across more than one column or row. Two attributes handle that:
colspanmakes a cell span multiple columns.rowspanmakes a cell span multiple rows.
For example, a cell that spans two columns:
<table>
<tr>
<th colspan="2">Contact Information</th>
</tr>
<tr>
<td>Email</td>
<td>support@acy-partner.com</td>
</tr>
</table>
The colspan="2" makes that header cell as wide as the two columns below it. rowspan does the same thing, just vertically. You won’t reach for these often, but they’re a lifesaver when your data genuinely has merged headers or grouped cells.
The big mistake: don’t use tables for page layout
This is the single most important warning in the whole article. Tables are for tabular data — and only for tabular data. Don’t use them to lay out the overall structure of a page.
Years ago, before modern CSS existed, developers misused tables to position everything on a page — header in one cell, sidebar in another, content in a third. It worked visually, but it was a bad idea, and today it’s firmly considered wrong.
Tables are for data, not layout
If you find yourself reaching for a table to put your sidebar next to your main content, or to arrange a page into columns, stop. That’s a job for CSS layout (using tools like Flexbox and Grid), not tables. Using tables for layout makes pages inaccessible — screen readers try to read the “data” and get hopelessly confused — and it makes the page rigid and hard to maintain. The rule is simple: if the content isn’t genuinely a grid of related data, don’t use a table.
So how do you tell whether something is tabular data? Ask yourself: would this make sense as a spreadsheet, with meaningful column headers and rows of related values? A price list, a schedule, a comparison chart — yes. The general layout of your page — no.
A practical example
Let’s finish with a realistic table you might actually build — a simple pricing comparison:
<table>
<caption>Plan comparison</caption>
<thead>
<tr>
<th>Feature</th>
<th>Basic</th>
<th>Pro</th>
</tr>
</thead>
<tbody>
<tr>
<td>Storage</td>
<td>10 GB</td>
<td>100 GB</td>
</tr>
<tr>
<td>Support</td>
<td>Email</td>
<td>Email + phone</td>
</tr>
</tbody>
</table>
This is exactly what tables are made for: rows and columns of genuinely related data, with clear headers. A reader — sighted or using a screen reader — can scan across a row to compare, or down a column to see every value for one feature. That clarity is the whole point of a table.
Wrapping up
You can now present structured data cleanly:
- A
<table>is built from rows (<tr>) containing header cells (<th>) and data cells (<td>). - Keep every row the same number of cells so the columns align.
- Use
<th>for headers — it carries meaning for accessibility, not just bold styling. - Add a
<caption>and group rows with<thead>and<tbody>for clarity on larger tables. - Use
colspanandrowspanwhen a cell needs to merge across columns or rows. - Never use tables for page layout — they’re for tabular data only; layout is CSS’s job.
That rounds out the structural HTML elements. You’ve now covered text, links, images, forms, and tables — the building blocks of real pages. In the final stretch of this section, we’ll step up to a more thoughtful way of writing HTML: semantic HTML, where you choose elements that describe the meaning of each part of your page, not just its appearance.