HTML & CSS

HTML Tables

Tables organize real data into rows and columns — pricing, schedules, stats, comparisons. Learn the anatomy, build one step by step, then make it readable and accessible.

New to tables? Read the visual lesson

When to use

Tables are for data, not layout

Reach for a table only when you have information that lives in a grid of rows and columns. For page layout, use Flexbox or Grid instead.

Good for tables

Pricing plans, class schedules, sports stats, spec sheets, financial reports — anything you'd put in a spreadsheet.

Not for layout

Don't use tables to position columns, sidebars, or cards. That's what CSS layout is for.

Rule of thumb

If the data has a natural header for each column or row, a table is the right tool.

Anatomy

The building-block tags

Every table is made from a small set of nested elements. Learn these six and you can build any table.

<table>

The wrapper around the entire table.

<caption>

A title for the table. First child of <table>, read aloud by screen readers.

<thead>

Groups the header row(s) at the top.

<tbody>

Groups the main data rows.

<tfoot>

Groups a summary/footer row, like totals.

<tr>

A table row — holds the cells.

<th>

A header cell. Bold & centered by default; labels a column or row.

<td>

A standard data cell.

Step 1

A basic table

Rows are built left to right with <tr>, and each cell is a <td>. The header row uses <th>.

NameRoleCity
Ada LovelaceEngineerLondon
Grace HopperAdmiralNew York
Katherine JohnsonMathematicianHampton

HTML

<table>
  <thead>
    <tr><th>Name</th><th>Role</th><th>City</th></tr>
  </thead>
  <tbody>
    <tr><td>Ada Lovelace</td><td>Engineer</td><td>London</td></tr>
    <tr><td>Grace Hopper</td><td>Admiral</td><td>New York</td></tr>
    <tr><td>Katherine Johnson</td><td>Mathematician</td><td>Hampton</td></tr>
  </tbody>
</table>
Step 2

Caption, totals, and spanning cells

Add a <caption> for a title, a <tfoot> for totals, and use colspan to merge a cell across columns. rowspan does the same down rows.

Q1 Sales by Region
RegionJanuaryFebruary
North$4,200$5,100
South$3,800$4,400
Total$8,000$9,500

HTML

<table>
  <caption>Q1 Sales by Region</caption>
  <thead>
    <tr><th>Region</th><th class="right">January</th><th class="right">February</th></tr>
  </thead>
  <tbody>
    <tr><td>North</td><td class="right">$4,200</td><td class="right">$5,100</td></tr>
    <tr><td>South</td><td class="right">$3,800</td><td class="right">$4,400</td></tr>
  </tbody>
  <tfoot>
    <tr><td>Total</td><td class="right">$8,000</td><td class="right">$9,500</td></tr>
  </tfoot>
</table>

<!-- "right" right-aligns numbers (see the CSS in Step 3).
     colspan merges a cell across columns; rowspan does it down rows: -->
<td colspan="2">Spans two columns</td>
Step 3

Make it readable with CSS

Raw tables are cramped. border-collapse merges the double borders, padding adds breathing room, and a zebra stripe with :nth-child(even) makes long tables easy to scan.

CSS

table { border-collapse: collapse; width: 100%; }

th, td {
  border: 1px solid #d2d2d7;
  padding: 10px 14px;
  text-align: left;
}

thead th { background: #06b6d4; color: #fff; }

/* Zebra striping */
tbody tr:nth-child(even) { background: #f5f5f7; }

/* Right-align numbers */
td.right { text-align: right; }
Tip: wrap wide tables in a scrolling container so they don't break small screens: <div style="overflow-x:auto"><table>…</table></div>
Do it right

Accessible tables

A few small habits make tables work for screen-reader users too.

Use real headers

Mark header cells with <th>, never a bold <td>. Add scope="col" or scope="row" so each header is tied to its column or row.

Always caption

A <caption> gives the table a name that's announced before the data.

Keep it a grid

Avoid merging cells unless the data truly needs it — complex spans are hard to navigate.

Related: see the Accessibility tutorial for more on building pages everyone can use.
Full Page

The whole thing as one file

Everything above — a captioned, accessible, zebra-striped data table with a totals row — combined into one complete HTML file. Copy it into a new file called tables.html, open it in a browser, and it just works — no libraries, no build step.

Live preview

Complete HTML — copy this whole file

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Q1 Sales by Region</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      color: #1d1d1f; background: #f5f5f7; line-height: 1.6;
      padding: 40px 20px;
    }
    .page { max-width: 640px; margin: 0 auto; }
    h1 { font-size: 1.8rem; margin-bottom: 6px; }
    .intro { color: #6e6e73; margin-bottom: 24px; }

    /* Wrap wide tables so they scroll instead of breaking small screens */
    .table-wrap { overflow-x: auto; background: #fff; border-radius: 12px; padding: 20px; }

    table { border-collapse: collapse; width: 100%; font-size: .95rem; }

    caption {
      caption-side: top; text-align: left; font-weight: 700;
      font-size: 1.05rem; padding-bottom: 12px;
    }

    th, td {
      border: 1px solid #d2d2d7;
      padding: 10px 14px;
      text-align: left;
    }

    thead th { background: #06b6d4; color: #fff; }

    /* Zebra striping makes long tables easy to scan */
    tbody tr:nth-child(even) { background: #f5f5f7; }

    tfoot td { font-weight: 700; background: #eef6f8; }

    /* Right-align numbers */
    .right { text-align: right; }
  </style>
</head>
<body>
  <div class="page">
    <h1>Quarterly Report</h1>
    <p class="intro">Regional sales for the first quarter, with monthly totals.</p>

    <div class="table-wrap">
      <table>
        <caption>Q1 Sales by Region</caption>
        <thead>
          <tr>
            <th scope="col">Region</th>
            <th scope="col" class="right">January</th>
            <th scope="col" class="right">February</th>
            <th scope="col" class="right">March</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th scope="row">North</th>
            <td class="right">$4,200</td>
            <td class="right">$5,100</td>
            <td class="right">$5,600</td>
          </tr>
          <tr>
            <th scope="row">South</th>
            <td class="right">$3,800</td>
            <td class="right">$4,400</td>
            <td class="right">$4,900</td>
          </tr>
          <tr>
            <th scope="row">West</th>
            <td class="right">$5,500</td>
            <td class="right">$6,000</td>
            <td class="right">$6,300</td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <th scope="row">Total</th>
            <td class="right">$13,500</td>
            <td class="right">$15,500</td>
            <td class="right">$16,800</td>
          </tr>
        </tfoot>
      </table>
    </div>
  </div>
</body>
</html>