Quick answer: An HTML table is built from <table>, <thead>, <tbody>, <tr>, <th> and <td>. Use <th scope> on header cells so screen readers can announce which row or column a value belongs to, and <caption> to label the table. Tables are for tabular data only — never for page layout.

The minimum correct HTML table

<table>
  <caption>Monthly signups</caption>
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Signups</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">January</th>
      <td>412</td>
    </tr>
    <tr>
      <th scope="row">February</th>
      <td>508</td>
    </tr>
  </tbody>
</table>
  • <caption> — a description of the table, announced by screen readers before the data.
  • <thead> and <tbody> — group header rows and data rows so styling and sticky headers are straightforward.
  • <th scope="col"> — a column header; scope="row" marks the first cell of a row as its label.
  • <td> — a data cell.

That scope attribute is the difference between a table a screen reader can navigate and a grid of unlabelled numbers.

Add it whenever the first cell of a row or column is a heading.

Merged cells

colspan spans a cell across columns and rowspan down rows. Both are used for grouped headers and summary rows.

<tr>
  <th scope="col" colspan="2">H1 2026</th>
</tr>
<tr>
  <th scope="col">Q1</th>
  <th scope="col">Q2</th>
</tr>
<tr>
  <td rowspan="2">12,940</td>
  <td>8,120</td>
</tr>

Keep spans small. A cell that spans many columns or rows is hard to track visually and harder for assistive technology to describe, so use them for genuine groupings rather than to achieve a decorative shape.

Making a wide table usable

Wide tables overflow on phones. The simplest fix wraps the table in a scrollable container, so the page itself never scrolls sideways.

<div class="table-wrap">
  <table>...</table>
</div>

<style>
.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 0.5rem 0.75rem; border-bottom: 1px solid #e2e8f0; text-align: left; }
thead th { position: sticky; top: 0; background: #f8fafc; }
</style>

border-collapse: collapse removes the double lines between cells, and position: sticky on the header keeps column names visible while the user scrolls a long table — a genuinely useful touch for data-heavy pages.

HTML tables accessibility checklist

  • Give every table a <caption> or an aria-label.
  • Use <th> with a scope for headers; do not style a <td> to look like a header.
  • Keep the reading order the same as the visual order.
  • Do not use a table for layout. Use CSS Grid for that.

Next: links and navigation. To learn HTML properly with a mentor, Ampersand Academy teaches web development one-to-one.

Frequently asked questions

Should I use tables for page layout?

No. Tables are for tabular data. Use CSS Grid or Flexbox for layout, and reserve table markup for genuine rows and columns of data.

What is the scope attribute on a th element?

It tells assistive technology whether a header labels a column or a row. Use scope=col for column headers and scope=row for the first cell of each row.

How do I make a table scroll on mobile?

Wrap the table in a div with overflow-x auto. The table scrolls inside that container while the rest of the page keeps its normal width.

What is the difference between colspan and rowspan?

colspan makes one cell span multiple columns, and rowspan makes it span multiple rows. Both are used for grouped headers and summary cells.

Why does my table have double borders?

Browsers draw separate borders by default. Set border-collapse: collapse on the table so adjacent cell borders merge into a single line.

Last updated on · Written by