Use an HTML table to present tabular data whose meaning depends on rows and columns. A basic accessible table uses <table>, table rows with <tr>, header cells with <th>, and data cells with <td>.
<table>
<caption>Student marks</caption>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Mark</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>85</td>
</tr>
</tbody>
</table>
| Name | Mark |
|---|---|
| Alice | 85 |
| Element | Purpose |
|---|---|
<table> | Contains the table. |
<caption> | Provides a title or description for the table. |
<thead> | Groups header rows. |
<tbody> | Groups the main data rows. |
<tfoot> | Groups summary or footer rows. |
<tr> | Defines one table row. |
<th> | Defines a header cell. |
<td> | Defines a data cell. |
<colgroup> | Groups columns for shared styling or metadata. |
<col> | Represents one or more columns inside a colgroup. |
A table row is created with <tr>. Each normal data cell inside the row uses <td>.
<table>
<tr>
<td>Row 1, Column 1</td>
<td>Row 1, Column 2</td>
</tr>
<tr>
<td>Row 2, Column 1</td>
<td>Row 2, Column 2</td>
</tr>
</table>
border="1" presentation attribute is obsolete in modern HTML. Apply borders and spacing with CSS instead.Use <th> for header cells. For simple tables, scope="col" or scope="row" makes the header relationship explicit.
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>25</td>
</tr>
</tbody>
</table>
<table>
<tr>
<th scope="row">Alice</th>
<td>85</td>
</tr>
</table>
Do not use <td> merely because you want a header to look different. Use semantic <th> and style it with CSS.
A <caption> gives the table an accessible title and helps readers understand what the data represents.
<table>
<caption>Monthly sales by product</caption>
<tr>
<th scope="col">Product</th>
<th scope="col">Sales</th>
</tr>
</table>
The caption belongs directly inside the table element.
Use table section elements to group related rows.
<table>
<caption>Order summary</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Laptop</td>
<td>$800</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$800</td>
</tr>
</tfoot>
</table>
These elements improve structure and also make table manipulation easier in CSS and JavaScript.
colspan lets one cell span several columns.
<table>
<tr>
<th colspan="2" scope="colgroup">Full Name</th>
</tr>
<tr>
<th scope="col">First</th>
<th scope="col">Last</th>
</tr>
</table>
Keep the logical number of columns consistent. A cell spanning two columns occupies the space of two normal cells.
rowspan lets one cell span several rows.
<table>
<tr>
<th rowspan="2" scope="rowgroup">Science</th>
<td>Physics</td>
</tr>
<tr>
<td>Chemistry</td>
</tr>
</table>
Use CSS for borders, spacing, alignment and colors instead of old HTML presentation attributes such as border, cellpadding, cellspacing, align or fixed presentation widths.
<style>
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
border: 1px solid #ccc;
padding: 0.5rem;
text-align: left;
}
.data-table tbody tr:nth-child(even) {
background: #f7f7f7;
}
</style>
<table class="data-table">
<!-- table content -->
</table>
Wide tables can overflow on small screens. A simple approach is to place the table inside a horizontally scrollable wrapper.
<div class="table-scroll" tabindex="0">
<table>
<!-- wide table -->
</table>
</div>
<style>
.table-scroll {
overflow-x: auto;
}
</style>
The wrapper preserves the table structure instead of hiding columns. The optional tabindex="0" can make the scrolling region keyboard-focusable when keyboard access to overflow is otherwise difficult; test the interaction in your target browsers and assistive technologies.
For most simple data tables:
<caption>.<th> for actual headers.scope="col" to column headers.scope="row" to row headers when rows have named headers.<table>
<caption>Quarterly revenue</caption>
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Q1</th>
<td>$20,000</td>
</tr>
</tbody>
</table>
For simple tables, scope is usually enough. For a genuinely complex table where a data cell belongs to several headers and the relationship is not clear from scope, the id and headers attributes can make the association explicit.
<table>
<thead>
<tr>
<th id="product">Product</th>
<th id="price">Price</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="product">Laptop</td>
<td headers="price">$800</td>
</tr>
</tbody>
</table>
headers attributes to every simple table by habit. Prefer the simplest correct header structure.Sorting is an application enhancement rather than part of the HTML table element itself. Keep the HTML structure semantic first, then add JavaScript behavior.
This compact example sorts the first <tbody> by a selected column:
<button type="button" onclick="sortTable(0)">Sort by name</button>
<table id="students">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
</tr>
</thead>
<tbody>
<tr><td>Bob</td><td>30</td></tr>
<tr><td>Alice</td><td>25</td></tr>
</tbody>
</table>
<script>
function sortTable(columnIndex) {
const tbody = document.querySelector("#students tbody");
const rows = Array.from(tbody.rows);
rows.sort((a, b) =>
a.cells[columnIndex].textContent.localeCompare(
b.cells[columnIndex].textContent
)
);
tbody.append(...rows);
}
</script>
<th> is not automatically an accessible control.<label for="tableSearch">Search names</label>
<input type="search" id="tableSearch">
<script>
const input = document.getElementById("tableSearch");
const rows = document.querySelectorAll("#students tbody tr");
input.addEventListener("input", () => {
const query = input.value.toLowerCase();
rows.forEach(row => {
const match = row.textContent.toLowerCase().includes(query);
row.hidden = !match;
});
});
</script>
This filters data rows while leaving the table header intact.
The original page included a CSV export example. When generating CSV, values containing commas, quotes or line breaks must be quoted correctly.
<button type="button" onclick="exportTableToCSV('table-data.csv')">
Download CSV
</button>
<script>
function csvCell(value) {
return '"' + value.replaceAll('"', '""') + '"';
}
function exportTableToCSV(filename) {
const rows = document.querySelectorAll("#students tr");
const lines = [];
rows.forEach(row => {
const cells = row.querySelectorAll("th, td");
lines.push(
Array.from(cells, cell => csvCell(cell.textContent.trim())).join(",")
);
});
const blob = new Blob(
[lines.join("\r\n")],
{ type: "text/csv;charset=utf-8" }
);
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
</script>
For large datasets, export from the server or data source rather than rebuilding a large CSV from the rendered DOM.
Plus2net pages use Bootstrap table classes for common presentation patterns.
<table class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
</tr>
</thead>
</table>
The existing Plus2net tutorial below covers adding a background image to a table.
Use background images only when they do not reduce text readability or obscure table data.
The old border attribute is obsolete for modern styling. Use CSS borders.
Use tables for data with row/column relationships. Use CSS Grid, Flexbox or other layout CSS for page structure.
Use <th> for real row or column headers and identify their scope where appropriate.
When the table needs a title or explanation, use <caption> so the description is structurally associated with the table.
Simple tables usually need only clear <th> cells and scope. Use id/headers when the header relationships are genuinely complex.
Use a horizontal scrolling wrapper or redesign the data presentation rather than shrinking all text until it becomes unreadable.
If table sorting is interactive, provide keyboard-operable controls and expose sort state appropriately.
CSV values containing commas, quotes or line breaks need proper quoting and quote escaping.
Use table for the table, tr for rows, th for header cells and td for data cells. Caption, thead, tbody and tfoot can add more structure.
No for modern presentation. Use CSS to add borders, spacing and other visual styles.
th represents a header cell, while td represents a normal data cell.
It identifies a th element as the header for a column. scope="row" identifies a row header.
colspan makes one table cell span across more than one column.
A common approach is to place the table in a wrapper with overflow-x:auto so users can scroll horizontally on smaller screens.
No. Use tables for tabular data and CSS layout tools for page structure.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.
| veerender | 04-10-2012 |
| thank u | |