HTML Tables: Rows, Cells, Headers, and Accessible Markup
An HTML table gives the browser the kind of grid you would recognise from a spreadsheet. Each row describes one record, each column describes one property, and the browser keeps those relationships connected as the table grows.
<table>
<tr>
<td>Espresso</td>
<td>$3.00</td>
</tr>
<tr>
<td>Cold brew</td>
<td>$4.50</td>
</tr>
</table> How an HTML Table Builds Its Grid
The table element wraps the complete grid. A tr creates one table row, while each td adds a data cell to that row. Columns appear because cells in the same position line up across rows, just as spreadsheet cells line up under the same column letter.
The first example has two rows with two cells each. Drink names form the first column and prices form the second, even though HTML has no separate column element for placing those values.
Add Headers with th and scope
The raw grid needs labels before its values have clear meaning. Replace the cells in the first row with th elements, then use scope="col" to state that each header labels the column below it.
<table>
<tr>
<th scope="col">Drink</th>
<th scope="col">Price</th>
</tr>
<tr>
<th scope="row">Espresso</th>
<td>$3.00</td>
</tr>
<tr>
<th scope="row">Cold brew</th>
<td>$4.50</td>
</tr>
</table> The drink cells now use scope="row" because each one labels the value beside it. Browsers can infer scope in a simple table, but explicit values help assistive technology, such as screen readers and similar tools, associate each price with the correct row and column headers.
Group Rows with a Caption and Table Sections
The next change names the spreadsheet grid and divides it into useful sections. Place caption first inside table, keep headings inside thead, put the main records in tbody, and reserve tfoot for a total or summary.
<table>
<caption>Coffee sales for Saturday</caption>
<thead>
<tr>
<th scope="col">Drink</th>
<th scope="col">Cups sold</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Espresso</th>
<td>18</td>
</tr>
<tr>
<th scope="row">Cold brew</th>
<td>12</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>30</td>
</tr>
</tfoot>
</table> The caption tells readers what the numbers describe before they enter the grid. The row groups also provide stable hooks for CSS and JavaScript, while keeping the source easy to inspect.
Span Cells Across the Grid
Some spreadsheets use a heading that covers several columns or a label that covers several rows. HTML uses colspan for horizontal coverage and rowspan for vertical coverage. The number is the count of grid slots occupied by that cell.
<table>
<caption>Weekend coffee sales</caption>
<colgroup span="1"></colgroup>
<colgroup span="2"></colgroup>
<thead>
<tr>
<th scope="col" rowspan="2">Drink</th>
<th scope="colgroup" colspan="2">Cups sold</th>
</tr>
<tr>
<th scope="col">Saturday</th>
<th scope="col">Sunday</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Espresso</th>
<td>18</td>
<td>21</td>
</tr>
<tr>
<th scope="row">Cold brew</th>
<td>12</td>
<td>15</td>
</tr>
</tbody>
</table> The two colgroup elements define the single Drink column and the two-column sales group, so scope="colgroup" explicitly associates Cups sold with both day columns. Every body row still fills three grid slots. Spanning changes the shape of the header without leaving gaps in the data beneath it.
Add CSS Styling Hooks
Keep classes focused on the table's visual role, such as sales-table, and leave the row and column meaning in the HTML elements. A common first rule is border-collapse: collapse, which makes adjacent cell borders share a single line.
<table class="sales-table">
<!-- caption, row groups, rows, and cells -->
</table>
<style>
.sales-table {
border-collapse: collapse;
}
</style> The CSS guide covers table widths, cell spacing, wrapping, and small-screen layouts. This page keeps the focus on HTML structure so the spreadsheet relationships remain correct before presentation rules are added.
Make Table Structure Accessible
A sighted reader can track a value by scanning up to a bold heading and left to a row label. Screen-reader users need those associations expressed in markup. Use th for real headers, set the relevant scope, and give the table a concise caption that explains its purpose.
HTML tables should represent meaningful rows and columns of data. Using table cells to position a sidebar, hero, or form adds relationships that the content does not have. CSS layout keeps visual placement separate from the document's data structure.
Common Pitfalls & Debugging
Headers Without Explicit Scope
A browser can infer the direction of simple headers, although some assistive technology may not make the same association reliably. Add scope="col" or scope="row" to each th, then check that every data cell has the headers you expect.
Uneven Cell Counts
A short row leaves an empty slot, which can shift values away from their intended headings. Count the occupied slots in each row, including any slots claimed by colspan or rowspan. Developer tools make the mismatch easier to spot when the source looks balanced.
Tables Used for Page Layout
Layout tables often look acceptable on a wide screen, then read as meaningless data grids to assistive technology and become difficult to rearrange on narrow screens. Replace the layout table with semantic page elements and handle their placement in CSS.
An Unexpected tbody in the DOM
Browsers may insert a tbody around rows written directly inside table. JavaScript selectors such as table > tr then fail because each row is a child of the inserted body. Write tbody explicitly and target the structure that appears in the DOM.
Frequently Asked Questions
When should you use an HTML table instead of CSS Grid?
Use an HTML table when the content has meaningful row and column relationships, such as prices, schedules, or results. Use CSS Grid when the columns exist only to arrange page sections or components visually.
How do you make an HTML table responsive?
Keep the semantic table markup, then use CSS to control width, wrapping, and small-screen presentation. Test the result at narrow viewports without removing headers or changing the relationships between cells.
Are layout tables ever acceptable in modern HTML?
Some HTML email templates still use layout tables because older mail clients support limited CSS. Keep the structure as simple as the email permits, add presentation roles where appropriate, and test it with the actual mail clients and assistive technologies your audience uses.
Do you need thead, tbody, and tfoot for every table, or only for larger ones?
A short table works with plain rows, but adding thead, tbody, and tfoot costs little and gives CSS and JavaScript stable hooks even in a small table. Reach for them by default rather than adding them once a table grows past some size.
Is a table appropriate for a small comparison of two or three items?
Yes, when the items have real properties being compared, such as price or specification columns. Table size does not decide whether content is tabular; that depends on the relationships between the data, not the row count.
Does a table need column widths, or can the browser size columns automatically?
A browser sizes table columns automatically from cell content and available space unless CSS overrides it. Specify widths only when a design requires a particular proportion; automatic sizing keeps a data-driven table readable without extra CSS.
Related HTML Guides
- HTML guide: move up to the language hub
- HTML table structure: practise the
table,tr, andtdgrid - HTML table headers and captions: add row groups, captions, and scoped headers
- HTML spanning cells: work through more
colspanandrowspanexamples - CSV to HTML table: turn spreadsheet rows into semantic markup
Sources
-
[1]
The Table element(developer.mozilla.org)
-
[2]
HTML table accessibility(developer.mozilla.org)
-
[3]
The Table Body element(developer.mozilla.org)
-
[4]
border-collapse(developer.mozilla.org)
Read Next
The table, tr, and td elements and the grid model: how rows and cells nest to form columns automatically in an HTML table.
Use th, thead, tbody, tfoot, caption, and the scope attribute to label an HTML table so browsers and screen readers understand its rows and columns.
Use colspan and rowspan to let one HTML table cell cover several columns or rows, and keep the grid math straight so columns stay aligned.
Load a CSV file in the browser, parse it correctly, render a real accessible table, and follow the path into SQL when the preview becomes an import.