---
title: "HTML Table Generator: Semantic Markup & CSS"
description: "Free HTML table generator with live preview. Create semantic table markup with proper thead, scope attributes, zebra striping, and copyable CSS."
url: https://frontendhelpers.com/html-table-generator
updated: 2026-10-12
---

# HTML table generator

Build clean, semantic table markup with accessible headers and a live preview — plus a starter stylesheet to make it look right.

## How to use this tool

Sighted readers understand a table instantly because they can see the grid. Screen reader users can't — they navigate cell by cell, and rely entirely on the markup to know what each value means.

That's what `<thead>`, `<th>` and the `scope` attribute provide. With them, moving through a table announces "Revenue, Q3: £48,000" — the header travels with the value. Without them, it announces "48,000" and the user has to remember which column they're in. On a wide table, that's the difference between usable and useless.

`scope="col"` marks a header for its column; `scope="row"` marks one for its row. Adding a `<caption>` is worth doing too — it gives the whole table a title that assistive technology announces on entry.

## The layout-tables myth, and its modern inversion

In the 1990s, tables were the only reliable way to build page layouts, and the practice persisted long after CSS could do the job properly. The correction — "never use tables" — became received wisdom.

It over-corrected. The rule was always _don't use tables for layout_, not _don't use tables_. Today the more common mistake is the reverse: building tabular data out of divs and flexbox because tables feel outdated. That throws away every accessibility benefit above, plus keyboard navigation and the browser's own column alignment, and gains nothing — CSS can style a real `<table>` however you like.

The test is simple: if your content is rows and columns of related values, it is a table. Use one.

## Making tables work on mobile

Wide tables are the genuine hard problem in responsive design. Three approaches work, depending on the data.

**Horizontal scroll** is the simplest and safest: wrap the table in a container with `overflow-x: auto`, keeping the structure intact. Add `tabindex="0"` to that wrapper so keyboard users can scroll it too — an easily missed accessibility detail. **Stacked cards** re-flow each row into a block with labels shown inline, which reads well on phones but needs the labels duplicating in CSS or data attributes. **Column hiding** shows only essential columns on narrow screens, which works when some columns are genuinely secondary.

## Styling that aids comprehension

A few choices measurably improve how quickly people read a table. Zebra striping helps the eye track across long rows. Right-aligning numeric columns lines up the digits so magnitudes are comparable at a glance — a detail that separates a considered table from a default one. Generous cell padding beats tight borders for readability, and a sticky header row keeps context visible while scrolling long data sets.

## Rendering API data as a table in React without losing the semantics

The usual shortcut is to take `Object.keys(rows[0])` as the headers and the array index as the key. It breaks in three predictable ways. If the first row is missing an optional field, a whole column disappears. Raw keys such as `created_at` become visible headings. And once the user sorts or filters, index keys make React reuse the wrong row's DOM and state, so a ticked checkbox or an expanded row jumps to a different record. Declare the columns once, with labels and formatting, and key each row by a stable ID from the data.

```jsx
// Columns are declared, not guessed from Object.keys(rows[0])
const COLUMNS = [
  { key: 'region', label: 'Region', rowHeader: true },
  { key: 'q3', label: 'Q3 revenue', numeric: true },
  { key: 'q4', label: 'Q4 revenue', numeric: true },
];
const gbp = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' });

export function RevenueTable({ rows, caption }) {
  return (
    <table>
      <caption>{caption}</caption>
      <thead>
        <tr>{COLUMNS.map(c => <th key={c.key} scope="col">{c.label}</th>)}</tr>
      </thead>
      <tbody>
        {rows.map(row => (
          <tr key={row.id}>{/* a stable id, never the array index */}
            {COLUMNS.map(c => c.rowHeader
              ? <th key={c.key} scope="row">{row[c.key]}</th>
              : <td key={c.key} className={c.numeric ? 'num' : undefined}>
                  {c.numeric ? gbp.format(row[c.key]) : row[c.key]}
                </td>)}
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

The first cell of each row is a `<th scope="row">`, so screen readers can announce the region alongside each figure as the user moves along the row. Amounts go through `Intl.NumberFormat` rather than string concatenation, and the `num` class should set `text-align: right` and `font-variant-numeric: tabular-nums` so the digits line up. If you add sorting, put a `<button>` inside each sortable `<th>` and set `aria-sort` on the column currently sorted. React explains why index keys cause trouble in [Rendering Lists](https://react.dev/learn/rendering-lists), and the W3C's [tables tutorial](https://www.w3.org/WAI/tutorials/tables/) covers headers for more complex layouts.

## Frequently asked questions

### Is it bad practice to use HTML tables?

Only for page layout. For genuine tabular data — rows and columns of related values — a real table element is the correct, accessible and SEO-friendly choice. Building tables out of divs discards accessibility for no benefit.

### What does scope="col" do in a table?

It tells assistive technology that a th cell is the header for its column, so screen readers announce the header alongside each value — "Revenue, Q3: £48,000" instead of an unlabelled number. Use scope="row" for row headers.

### How do I make an HTML table responsive?

The simplest reliable method is wrapping it in a container with overflow-x: auto so it scrolls horizontally with its structure intact. Add tabindex="0" to that wrapper so keyboard users can scroll it as well.

### Should I add a caption to my table?

Yes where the table needs a title. A caption element gives the table an accessible name that screen readers announce on entry, providing context before the user starts navigating cells.

## Related tools

- [HTML Entity Encoder](https://frontendhelpers.com/html-entity-encoder): Escape special characters safely inside your table cells.
 
- [WCAG Contrast Checker](https://frontendhelpers.com/contrast-checker): Check your table text and zebra stripes pass contrast requirements.
 
- [Accessibility essentials](https://frontendhelpers.com/essentials#ess-a11y): Semantic HTML, keyboard navigation and the checks that matter.

---

This page is a free interactive tool. Use it in your browser at https://frontendhelpers.com/html-table-generator
