HTML · CSS

HTML Table Generator

Build a semantic table with the correct thead, tbody and tfoot structure, then copy or download the HTML and CSS. The exported styles use neutral greys, so they read correctly on a white page and on a dark one without any edits.

Updated 21 October 2026 Built by Khalil Rahman 100% client-side No sign-up

Table generator

Runs locally
Rows inside tbody
Leave blank to use Header 1, Header 2, and so on.
Copy cells from Excel or Google Sheets and paste here. The row and column counts update to match. If the first row is headers, leave the header-row option on.
Structure and style

Ready.

Live preview

Cells are editable

Click any cell to edit its text. The HTML output updates as you type.

HTML

CSS

What this generator does

Hand-writing table markup is repetitive and easy to get subtly wrong: an unclosed row, a missing scope attribute, a header row that ends up in tbody instead of thead. This tool builds the structure for you, then lets you edit the content in place so you do not have to touch the HTML at all unless you want to.

The output is deliberately plain. No framework classes, no inline styles, no dependencies — just semantic HTML and a small, readable stylesheet you can paste into any project. That makes it useful in places where a heavier component library would be overkill: a marketing page, an email template's companion web version, a documentation table, a static site, a README rendered by a Markdown processor that allows raw HTML.

Everything runs in your browser. There is no upload, no account, and no request made when you generate, edit or copy.

Table anatomy

An HTML table has four structural layers. Two are mandatory, two are optional but strongly recommended.

HTML table elements and their purposes
ElementPurposeRequired?
<table>The container. Everything else lives inside it.Yes
<tr>One row. Contains cells.Yes
<th>A header cell. Bold and centred by default, and announced as a header by screen readers.Usually
<td>A data cell.Usually
<caption>A title that names the whole table. Must be the first child of table.No, but recommended
<thead>Groups the header row or rows.No, but recommended
<tbody>Groups the body rows.No, but browsers imply it
<tfoot>Groups summary or footer rows. In HTML5 it must come after tbody.No

Why the sections matter

Without thead and tbody, a table is just a pile of rows. With them, several things start working:

  • Screen readers can announce which section a cell belongs to and read the header when you move between data cells.
  • Printing repeats the thead on every page of a long table, so the reader does not lose the column names.
  • Styling becomes easier — you can target header cells with thead th instead of :first-child gymnastics.
  • Scripts can sort or filter tbody without touching the header or footer.

Making the table accessible

A table that looks fine can still be unusable with a screen reader. Three attributes do most of the work, and the generator sets the first automatically.

Caption

The caption element gives the table an accessible name. Without one, a screen reader may announce it as "table, 5 rows, 4 columns" with no indication of what it contains. Put the caption on every table that carries real data — it is not decoration, it is the table's identity.

scope

Each th in the header row gets scope="col". This tells assistive technology that the cell labels the column below it. A table with row headers uses scope="row" on the first cell of each row instead. The generator emits column scope because its default data model is column-headed; if your own data has row headers, edit the output to swap them.

Headers attribute

For a table with an irregular structure, or one where merged cells break the simple row/column relationship, each td can point at its header cells by id:

<th id="price" scope="col">Price</th>
...
<td headers="price">£19.99</td>

This is rarely necessary in a simple grid, and the generator does not emit it. It matters when you have two sets of headers, or merged cells that would otherwise confuse the announcement.

What to check before publishing

  • Does the table have a caption? If not, add one.
  • Do the header cells have scope?
  • Is there any content in a cell that only makes sense visually — a colour coding, an arrow, a decorative icon? Add a text equivalent.
  • At a narrow viewport, does the table scroll sideways rather than squash to unreadable widths? The responsive wrapper handles this.

When not to use a table

The table element is for tabular data: information where the row and column positions carry meaning, such as a price list, a comparison, a schedule, or a set of statistics. It is not a layout tool.

Why the old "tables for layout" approach is bad

  • Screen readers announce it as a data table, so users are told "table, 12 rows" for what is really a header, a sidebar and a footer. Navigating becomes confusing.
  • It does not reflow. A three-column table stays a three-column table at 320 px wide, producing horizontal scrolling or truncated content.
  • It fights your CSS. Flexbox and grid are designed for layout; using a table means overriding default cell display, border collapse and vertical alignment on every element.
  • It is bad for SEO. Content wrapped in a maze of tables is harder for a crawler to interpret, and internal link hierarchy gets lost.

Use instead

CSS Grid for two-dimensional layout, flexbox for one-dimensional rows and columns, and ordinary semantic elements — header, nav, main, aside, footer, section, article — for structure. The only case where a table still makes sense outside of data is an actual email template, because email clients have poor support for modern CSS layout. Even there, prefer a simple single-column design and reserve tables for genuinely tabular content.

Colspan and rowspan

The generator produces a rectangular grid. Merging cells is deliberately out of scope, because a merge changes the grid in ways a rows-and-columns input cannot express — a cell that spans two columns removes one cell from the row, and a cell that spans two rows affects the row below it.

To add merged cells to the output, edit the HTML directly. Two attributes do the work.

colspan

Makes a cell stretch horizontally across several columns. The other cells in the same row must be removed to keep the total count correct.

<tr>
  <th scope="col" colspan="2">Name</th>
  <th scope="col">Role</th>
</tr>
<tr>
  <td colspan="2">Ada Lovelace</td>
  <td>Engineer</td>
</tr>

rowspan

Makes a cell stretch vertically. The rows below lose a cell at that position.

<tr>
  <td rowspan="2">Engineering</td>
  <td>Ada Lovelace</td>
</tr>
<tr>
  <!-- the first cell is already occupied by the rowspan above -->
  <td>Grace Hopper</td>
</tr>
A merged cell must still be readable out of context. If a header spans two columns, a screen reader will announce it once per column — good. If a data cell spans several rows, a reader moving through the table may not know which row header applies. When in doubt, prefer separate cells with repeated content over a merge.

Styling notes

The exported CSS uses rgba(128, 128, 128, …) rather than fixed hex colours. A mid-grey at low alpha reads as a subtle line or tint on a white background and as a subtle lightening on a dark one, so the same stylesheet works on either without editing. If you want exact brand colours, replace the four rgba values with your own tokens.

What each rule does

  • border-collapse: collapse — single-pixel borders shared between adjacent cells, instead of the default doubled line.
  • thead th background — a tint that separates the header from the body without looking like a button.
  • tbody tr:nth-child(even) — the striped-row effect, optional and easy to remove.
  • tbody tr:hover — a highlight when the pointer is over a row, useful for wide tables where the eye can drift.
  • The @media (max-width: 640px) block reduces padding and font size so the table stays readable on a phone without horizontal scrolling for small tables.

The responsive wrapper

Wrapping the table in <div class="table-responsive"> with overflow-x: auto is the simplest way to keep a wide table usable on a narrow screen. The table stays at its natural width and the container scrolls sideways. It is not a perfect solution — horizontal scrolling is annoying — but it is far better than a table that squashes into unreadable columns or overflows the viewport and breaks the page layout.

What this generator cannot do

  • No merged cells through the interface. colspan and rowspan need manual editing; see the section above for worked examples.
  • No row headers. The default structure labels columns, not rows. If your data is a two-dimensional matrix where each row also has a label, edit the first cell of each row into a th scope="row".
  • No sorting, filtering or pagination. The output is static markup. Adding interactivity needs JavaScript of your own.
  • No styling presets. One neutral stylesheet, no theme gallery. Design choices belong in your own CSS.
  • No Markdown output. The output is HTML. Convert separately if you are targeting a Markdown file that only supports pipe tables, which cannot express captions or scoped headers anyway.
  • No data import from CSV files. Paste from a spreadsheet works (tabs and newlines); uploading a .csv file does not.
  • Content is stored only in the page's memory. Reload the tab and your edits are gone. Copy or download before closing.

Frequently asked questions

Is the generated table accessible?

The markup is as accessible as the options you enable. With the header row on, header cells are th elements inside a thead and each carries scope="col", which is what lets a screen reader announce the column name when a data cell is read. With the caption option on, the caption element gives the table an accessible name. Turning both off produces valid HTML but an unlabelled table, which is hard to navigate with assistive technology.

What do the row and column counts mean?

Body rows means rows in the tbody. If you enable the header row, one extra row appears above it in a thead. If you enable the footer row, one extra row appears below it in a tfoot. So five body rows with both options enabled produces seven visible rows and a cleaner semantic structure than treating one of the body rows as a header.

Will the generated CSS work on a dark website?

Yes. The CSS uses rgba(128, 128, 128, …) for borders, header backgrounds and hover states rather than hard-coded hex values. A mid-grey with low alpha reads as a subtle line or tint on a white background and as a subtle lightening on a dark one, so the same stylesheet works on either without editing. If you want exact colours, override the four rgba values with your own theme tokens.

Can I edit the cell contents?

Yes. Every cell in the live preview is editable, so click into it and type. The HTML output updates as you edit, and your changes survive option changes. If you change the row or column count, content that no longer fits is discarded, because the coordinates have changed.

Can I paste data from Excel or Google Sheets?

Yes. Copy the cells in your spreadsheet and paste them into the data box. Tabs separate columns and newlines separate rows, which is exactly what a spreadsheet puts on the clipboard. The row and column counts update to match the pasted data. If the first row is meant to be headers, leave the header-row option on.

Does it support colspan and rowspan?

Not through the interface, because merging cells changes the grid in ways that a simple row-and-column input cannot express. The generated markup is the correct starting point, and the page shows a worked example of adding colspan and rowspan by hand. For anything more than one or two merged cells, editing the HTML directly is faster than a visual merge tool.

Should I use a table for page layout?

No. Tables are for tabular data — rows and columns where the position of a cell carries meaning, such as a price list or a schedule. Using a table to position content on a page breaks screen-reader navigation, does not reflow on mobile, and is not how modern CSS layout works. Use flexbox or grid for layout and reserve the table element for data.

Is anything uploaded when I use this tool?

No. The table is built in JavaScript on your device. There is no API call and no analytics event containing your data. You can confirm this in the Network tab: paste data into the box and watch that no request is made. The page works with the connection off once it has loaded.

About this tool

Built and reviewed by Khalil Rahman, founder of SkillsInsider. Last reviewed 21 October 2026.

Output is checked against the HTML Living Standard's table model and against WCAG 2.2 criteria 1.3.1 (Info and Relationships) and 2.4.6 (Headings and Labels) whenever the generator changes. If a generated table fails a validator or an accessibility checker, report it through the contact page with the options you used.

Editorial and verification standards are in the editorial policy. Data handling is in the privacy policy.