Guides › HTML
How to make a table in HTML
An HTML table is a <table> element with rows of cells. HTML gives the table
its structure, and CSS gives it its look: borders, spacing, colours. This guide shows the elements,
the CSS that makes a table easy to read, merged cells, tables that work on phones and in e-mail,
and how to move tables between web pages and spreadsheets. Each example shows the code and the
table that a browser draws from it.
The elements of a table
<table>
<thead>
<tr>
<th>Planet</th>
<th>Diameter (km)</th>
<th>Moons</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mercury</td>
<td>4879</td>
<td>0</td>
</tr>
<tr>
<td>Venus</td>
<td>12104</td>
<td>0</td>
</tr>
<tr>
<td>Earth</td>
<td>12756</td>
<td>1</td>
</tr>
<tr>
<td>Mars</td>
<td>6792</td>
<td>2</td>
</tr>
</tbody>
</table>
| Planet | Diameter (km) | Moons |
|---|---|---|
| Mercury | 4879 | 0 |
| Venus | 12104 | 0 |
| Earth | 12756 | 1 |
| Mars | 6792 | 2 |
| Element | What it is |
|---|---|
<table> | The table. Everything else goes in it. |
<tr> | A row (table row). |
<td> | A cell with data (table data). |
<th> | A header cell. Browsers show its text bold and centred. |
<thead>, <tbody>, <tfoot> |
The header rows, the body rows and the footer rows (a total, for example). They are
optional, but they help CSS, screen readers and printing: a printed table repeats its
<thead> on each page. |
<caption> | The table's title. See Captions. |
Write the cells of a row from left to right. Each row should have as many cells as the others (a merged cell counts as the cells it covers). The browser does not complain about a missing cell, but the table then has a hole at the end of the row.
The table above has no lines. That is how browsers draw a table without CSS. The old
border="1" attribute still draws lines, but CSS does it better.
Borders, spacing and stripes with CSS
Give the table a class and style it in a <style> element, or in your site's
CSS file:
<style>
.planets {
border-collapse: collapse;
}
.planets th,
.planets td {
padding: 6px 12px;
border-bottom: 1px solid #ccc;
text-align: left;
}
.planets thead th {
border-bottom: 2px solid #333;
}
.planets tbody tr:nth-child(even) {
background: #f3f5f8;
}
.planets .num {
text-align: right;
}
</style>
<table class="planets">
<thead>
<tr>
<th>Planet</th>
<th class="num">Diameter (km)</th>
<th class="num">Moons</th>
</tr>
</thead>
<tbody>
<tr><td>Mercury</td><td class="num">4879</td><td class="num">0</td></tr>
<tr><td>Venus</td><td class="num">12104</td><td class="num">0</td></tr>
<tr><td>Earth</td><td class="num">12756</td><td class="num">1</td></tr>
<tr><td>Mars</td><td class="num">6792</td><td class="num">2</td></tr>
</tbody>
</table>
| Planet | Diameter (km) | Moons |
|---|---|---|
| Mercury | 4879 | 0 |
| Venus | 12104 | 0 |
| Earth | 12756 | 1 |
| Mars | 6792 | 2 |
border-collapse: collapsejoins the borders of neighbouring cells into one line. Without it, each cell has its own border with a gap between them, and the table looks like a grid of boxes.paddingis the space between the cell's text and its border. 6 to 12 pixels make a table easy to read.- Lines under the rows, and none between the columns, make a quiet table. The thicker line under the header shows where the data starts.
tr:nth-child(even)colours every second row (zebra stripes). Stripes help the eye follow a row across a wide table.- Numbers to the right. A class for the number cells
(
numhere) aligns them, so that the units, tens and hundreds are one under the other. For digits of the same width in any font, addfont-variant-numeric: tabular-nums.
Style the table by its class, not all tables with table { ... }. Otherwise the
styles change every table of your site, including tables in plugins and widgets.
Merge cells: colspan and rowspan
colspan merges a cell with the cells to its right. rowspan merges it
with the cells under it:
<style>
table { border-collapse: collapse; }
th, td { padding: 4px 10px; border: 1px solid #999; }
</style>
<table>
<tr>
<th rowspan="2">Planet</th>
<th colspan="2">Size</th>
</tr>
<tr>
<th>Diameter (km)</th>
<th>Mass (10<sup>24</sup> kg)</th>
</tr>
<tr>
<td>Earth</td>
<td>12756</td>
<td>5.97</td>
</tr>
<tr>
<td>Mars</td>
<td>6792</td>
<td>0.642</td>
</tr>
</table>
| Planet | Size | |
|---|---|---|
| Diameter (km) | Mass (1024 kg) | |
| Earth | 12756 | 5.97 |
| Mars | 6792 | 0.642 |
<th colspan="2">takes the place of two cells in its row.<th rowspan="2">takes the place of a cell in its row and in the row under it. So the second row has only two cells: Planet is already in its first column.- This is the common mistake: writing a cell where a merged cell already is. The row is then one cell too long, and its last cell sticks out of the table. Count the cells of each row, with the cells that merged cells from above take.
In our editor Select the cells and click the merge button on the toolbar. The generator
writes the colspan and rowspan and leaves out the covered cells.
Captions and header cells for screen readers
A screen reader reads a table cell by cell. With header cells marked, it can tell the listener
which column and row each cell is in. Use <th> for the headers of the columns,
and for the first cell of each row when it names the row:
<style>
table { border-collapse: collapse; }
caption { margin-bottom: 6px; font-weight: bold; }
th, td { padding: 4px 10px; border: 1px solid #999; }
tbody th { text-align: left; }
</style>
<table>
<caption>The two largest moons of each planet</caption>
<thead>
<tr>
<th scope="col">Planet</th>
<th scope="col">Largest moon</th>
<th scope="col">Second largest</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Earth</th>
<td>Moon</td>
<td>none</td>
</tr>
<tr>
<th scope="row">Mars</th>
<td>Phobos</td>
<td>Deimos</td>
</tr>
<tr>
<th scope="row">Jupiter</th>
<td>Ganymede</td>
<td>Callisto</td>
</tr>
</tbody>
</table>
| Planet | Largest moon | Second largest |
|---|---|---|
| Earth | Moon | none |
| Mars | Phobos | Deimos |
| Jupiter | Ganymede | Callisto |
<caption>is the table's title. It must be the first element in the<table>. Browsers show it above the table.caption-side: bottomputs it under the table.scope="col"says that a header cell is the header of its column,scope="row"of its row. Browsers can guess this in simple tables. Write it in tables with headers in both directions, as here.- Do not leave the header cells empty, and do not put a table's header in a picture.
Column widths and long text
A browser makes each column as wide as its content needs, and breaks long text into lines when the
table does not fit. To set the widths yourself, use table-layout: fixed and give the
columns widths:
<style>
.fixed {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.fixed th, .fixed td {
padding: 4px 8px;
border: 1px solid #999;
text-align: left;
vertical-align: top;
overflow-wrap: break-word;
}
</style>
<table class="fixed">
<colgroup>
<col style="width: 25%">
<col style="width: 75%">
</colgroup>
<tr>
<th>Planet</th>
<th>Surface</th>
</tr>
<tr>
<td>Mercury</td>
<td>Grey rock with many craters, very hot by day and very cold by night</td>
</tr>
<tr>
<td>Venus</td>
<td>Hidden under thick clouds of sulfuric acid</td>
</tr>
</table>
| Planet | Surface |
|---|---|
| Mercury | Grey rock with many craters, very hot by day and very cold by night |
| Venus | Hidden under thick clouds of sulfuric acid |
table-layout: fixedtakes the widths from the<col>elements, or from the first row, and does not look at the rest of the content. The table needs a width too, such as100%. A large table also shows faster this way.<colgroup>and<col>set the width (or the background) of a whole column. Other properties, such astext-align, do not work on<col>: use a class on the cells.overflow-wrap: break-wordbreaks a long word or URL that does not fit in its cell.white-space: nowrapkeeps the text of a cell on one line, for dates and numbers.vertical-align: topstarts the text of each cell at the top of the row. By default, browsers centre it vertically, which is hard to read when the cells of a row have different numbers of lines.
Wide and long tables: scrolling and a fixed header
A table with many columns does not fit on a phone. The browser then shrinks the columns until the text is a few letters wide, or the page scrolls sideways. Put the table in a box that scrolls instead:
<style>
.table-scroll {
max-height: 150px;
overflow: auto;
}
.table-scroll table {
border-collapse: collapse;
}
.table-scroll th, .table-scroll td {
padding: 4px 10px;
border-bottom: 1px solid #ccc;
white-space: nowrap;
}
.table-scroll thead th {
position: sticky;
top: 0;
background: #fff;
border-bottom: 2px solid #333;
}
</style>
<div class="table-scroll">
<table>
<thead>
<tr><th>Planet</th><th>Diameter (km)</th><th>Gravity (m/s²)</th><th>Day (hours)</th><th>Year (days)</th></tr>
</thead>
<tbody>
<tr><td>Mercury</td><td>4879</td><td>3.7</td><td>4222.6</td><td>88</td></tr>
<tr><td>Venus</td><td>12104</td><td>8.9</td><td>2802.0</td><td>225</td></tr>
<tr><td>Earth</td><td>12756</td><td>9.8</td><td>24.0</td><td>365</td></tr>
<tr><td>Mars</td><td>6792</td><td>3.7</td><td>24.7</td><td>687</td></tr>
<tr><td>Jupiter</td><td>142984</td><td>23.1</td><td>9.9</td><td>4331</td></tr>
<tr><td>Saturn</td><td>120536</td><td>9.0</td><td>10.7</td><td>10747</td></tr>
<tr><td>Uranus</td><td>51118</td><td>8.7</td><td>17.2</td><td>30589</td></tr>
<tr><td>Neptune</td><td>49528</td><td>11.0</td><td>16.1</td><td>59800</td></tr>
</tbody>
</table>
</div>
| Planet | Diameter (km) | Gravity (m/s²) | Day (hours) | Year (days) |
|---|---|---|---|---|
| Mercury | 4879 | 3.7 | 4222.6 | 88 |
| Venus | 12104 | 8.9 | 2802.0 | 225 |
| Earth | 12756 | 9.8 | 24.0 | 365 |
| Mars | 6792 | 3.7 | 24.7 | 687 |
| Jupiter | 142984 | 23.1 | 9.9 | 4331 |
| Saturn | 120536 | 9.0 | 10.7 | 10747 |
| Uranus | 51118 | 8.7 | 17.2 | 30589 |
| Neptune | 49528 | 11.0 | 16.1 | 59800 |
overflow: autoon the<div>gives it a scroll bar when the table is wider than the box. Only the table scrolls, not the page. Useoverflow-x: autoto scroll only sideways.max-heightmakes a long table scroll inside the box (here, 150 pixels, to show it). Without it, the page scrolls.position: sticky; top: 0on the header cells keeps the header in view while the rows scroll under it. Give the header cells a background, or the rows show through them.
Tables in e-mail
E-mail programs show HTML, but many of them remove <style> elements, or ignore
parts of CSS. The classic Outlook for Windows draws e-mails with Microsoft Word, which knows little
CSS. For a
table in an e-mail:
<table style="border-collapse: collapse;">
<tr>
<th style="padding: 6px 12px; border-bottom: 2px solid #333333; text-align: left;">Item</th>
<th style="padding: 6px 12px; border-bottom: 2px solid #333333; text-align: right;">Price</th>
</tr>
<tr>
<td style="padding: 6px 12px; border-bottom: 1px solid #cccccc;">Coffee</td>
<td style="padding: 6px 12px; border-bottom: 1px solid #cccccc; text-align: right;">7.00</td>
</tr>
<tr>
<td style="padding: 6px 12px; font-weight: bold;">Total</td>
<td style="padding: 6px 12px; font-weight: bold; text-align: right;">7.00</td>
</tr>
</table>
| Item | Price |
|---|---|
| Coffee | 7.00 |
| Total | 7.00 |
- Put the styles in each cell's
styleattribute (inline styles). - Use simple properties:
border,padding,background-color,color,font-weight,text-align. Write colours with six digits (#cccccc). - Do not use
position: sticky, scrolling boxes or:nth-childstripes: colour the rows' cells one by one. - Test the e-mail: send it to yourself and open it in the programs that your readers use.
In our editor In the options under the code, select Styles: Inline (for e-mail).
Each cell then gets its own style attribute.
Old attributes and tables for layout
Old web pages and tutorials use attributes on the table:
| Old attribute | CSS today |
|---|---|
border="1" | border: 1px solid on the cells, and
border-collapse: collapse on the table |
cellpadding="5" | padding: 5px on the cells |
cellspacing="0" | border-collapse: collapse, or
border-spacing: 0 |
width="600" | width: 600px |
align="center" (on the table) | margin: 0 auto |
align="right" (on a cell) | text-align: right |
bgcolor="#eeeeee" | background-color: #eeeeee |
valign="top" | vertical-align: top |
Browsers still read these attributes, but they are no longer part of HTML. Use CSS, except in e-mails for old programs, where the attributes are sometimes the only thing that works.
Use tables for data: things that you compare in rows and columns. Do not use a table to place the parts of a page side by side. Screen readers read such a table as data, and it does not adapt to small screens. CSS grid and flexbox do this job.
From Excel to HTML, and from a web page to Excel
From Excel or Google Sheets to HTML
Select the cells and copy them. Open the HTML Tables Generator, click its top left cell and paste. The cells come with their merged cells, text style, colours, alignment and borders, and the HTML code is under the table. To import a file, select File › Import CSV or Excel file. See also Import an Excel file.
From a web page to Excel or Google Sheets
- Copy and paste. Select the table on the page, from its first cell to its last, copy it and paste it in the spreadsheet. This works for most tables.
- Excel: Data › From Web asks for the page's address and lists its tables. The table can then be refreshed when the page changes.
- Google Sheets:
=IMPORTHTML("https://...", "table", 1)reads the first table of the page, and keeps it up to date. - Python:
pandas.read_html(url)returns each table of the page as a data frame.
From HTML code to Markdown or LaTeX
In the Markdown or LaTeX generator, select File › Paste table data, paste the HTML code or the table copied from the page, and click Load.
Make the table without writing the code
In our HTML Tables Generator, you make the table in a grid, as in a spreadsheet, and the code is written as you type:
- The look: the Theme button chooses the borders, the header and the stripes. The toolbar sets the font, the colours, the alignment and the borders of the selected cells.
- Widths: drag the right edge of a column's header.
- The header: First row is a header writes it with
<th>in a<thead>. Keep it in view while scrolling makes it sticky, and Sort the rows by clicking it adds a small script that sorts the rows. - Phones: Scroll sideways on small screens puts the table in a box that scrolls.
- Styles: Style sheet writes a
<style>element, Inline (for e-mail) astyleattribute on each cell, and None only the table, for your own CSS. - See the result: click Preview.
Paste the code where the table should be: in an HTML block of WordPress or another site builder,
or in your page. Some site builders remove <style> elements or scripts. Then use
inline styles, and leave out sorting.