Tables are the right tool for data with rows and columns, and the wrong tool for layout.
Use a <table> when data really has two dimensions: a price list, a schedule, comparison data. Never use tables for page layout; that's what CSS grid and flexbox are for.
The structure:
<caption>as the first child gives the table a title, announced by screen readers.<thead>,<tbody>and<tfoot>group header, body and summary rows.<tr>is a row;<th>is a header cell and<td>a data cell.
Mark headers with <th> and add scope="col" or scope="row" so assistive technology knows which data cells each header labels. When a reader lands on a cell, it can then say "Price, Pro plan: $12" instead of just "$12".
Cells can span: colspan="2" stretches a cell across two columns and rowspan="3" down three rows. Complex spanning makes tables hard to read and navigate; often two simple tables are clearer.
Even if you leave out <tbody>, the parser inserts one, which matters when you write CSS like table > tr (it won't match). Wide tables overflow small screens: wrap the table in a <div> styled with overflow-x: auto so it scrolls horizontally instead of breaking the layout.
Example
<table>
<caption>Plans and prices (per month)</caption>
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Price</th>
<th scope="col">Projects</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">Free</th><td>$0</td><td>3</td></tr>
<tr><th scope="row">Pro</th><td>$12</td><td>Unlimited</td></tr>
</tbody>
<tfoot>
<tr><td colspan="3">Prices exclude VAT.</td></tr>
</tfoot>
</table>Remember
- Tables are for tabular data only, never layout
- Use caption, thead/tbody and th with scope
- colspan/rowspan merge cells; keep spanning simple
Watch out for
- Styling
table > trand wondering why nothing matches: the parser inserted a tbody.