Basic Table: table, tr, td
The three core table elements are:
<table>β the wrapper element for the entire table<tr>β a table row<td>β a table data cell (standard cell)
<table>
<tr>
<td>HTML</td>
<td>Structure</td>
<td>Beginner</td>
</tr>
<tr>
<td>CSS</td>
<td>Styling</td>
<td>Beginner</td>
</tr>
<tr>
<td>JavaScript</td>
<td>Interactivity</td>
<td>Intermediate</td>
</tr>
</table>
Table Headers β th and scope
The <th> element defines a header cell. Unlike <td>, a <th> is bold and centred by default, and it carries semantic meaning β it labels a column or row.
The scope attribute tells screen readers whether the header applies to a column (scope="col") or a row (scope="row").
<table>
<tr>
<th scope="col">Language</th>
<th scope="col">Purpose</th>
<th scope="col">Level</th>
</tr>
<tr>
<td>HTML</td>
<td>Structure</td>
<td>Beginner</td>
</tr>
<tr>
<td>CSS</td>
<td>Styling</td>
<td>Beginner</td>
</tr>
</table>
Table Structure: thead, tbody, tfoot
For any table beyond the simplest, wrap your rows in the semantic section elements:
<thead>β the header section (column labels)<tbody>β the body (data rows)<tfoot>β the footer (totals, notes)
These elements don't change visual rendering by default, but they allow browsers to keep the header and footer visible when the table body scrolls, and they help screen readers and CSS target sections independently.
<table>
<thead>
<tr>
<th scope="col">Course</th>
<th scope="col">Lessons</th>
<th scope="col">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>HTML Fundamentals</td>
<td>24</td>
<td>3 hours</td>
</tr>
<tr>
<td>CSS Basics</td>
<td>30</td>
<td>4 hours</td>
</tr>
<tr>
<td>JavaScript Essentials</td>
<td>45</td>
<td>6 hours</td>
</tr>
</tbody>
<tfoot>
<tr>
<td><strong>Total</strong></td>
<td><strong>99</strong></td>
<td><strong>13 hours</strong></td>
</tr>
</tfoot>
</table>
The caption Element
The <caption> element provides a visible title for a table. It must be the first child of <table>. Both sighted users and screen readers benefit from a clear caption β it explains what the table is about without relying on surrounding text.
<table>
<caption>ylearner Course Catalogue β 2026</caption>
<thead>
<tr>
<th scope="col">Course</th>
<th scope="col">Level</th>
<th scope="col">Free?</th>
</tr>
</thead>
<tbody>
<tr><td>HTML</td><td>Beginner</td><td>Yes</td></tr>
<tr><td>Python</td><td>Beginner</td><td>Yes</td></tr>
<tr><td>JavaScript</td><td>Intermediate</td><td>Yes</td></tr>
</tbody>
</table>
colspan and rowspan β Merging Cells
Use colspan to make a cell span multiple columns, and rowspan to make a cell span multiple rows.
<!-- colspan: cell spans 2 columns -->
<table>
<caption>Weekly Schedule</caption>
<thead>
<tr>
<th scope="col">Time</th>
<th scope="col">Monday</th>
<th scope="col">Tuesday</th>
<th scope="col">Wednesday</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">9:00 AM</th>
<td colspan="2">HTML Workshop (spans Mon + Tue)</td>
<td>CSS Intro</td>
</tr>
<tr>
<th scope="row">11:00 AM</th>
<td>JS Basics</td>
<td rowspan="2">Project Work (spans 11 AM + 1 PM)</td>
<td>Python Intro</td>
</tr>
<tr>
<th scope="row">1:00 PM</th>
<td>Review</td>
<td>Git Basics</td>
</tr>
</tbody>
</table>
colspan="2", that row only needs 2 <td> elements total. A cell count mismatch causes columns to misalign.
Use Tables for Data Only
<table> when the content is genuinely tabular (it has meaningful rows AND meaningful columns).
Good use cases for tables:
- Comparison charts (features vs. pricing tiers)
- Timetables and schedules
- Sports results / league tables
- Financial data and reports
- Specification sheets
Accessibility Best Practices
An accessible table communicates the relationship between each data cell and its header. Key practices:
- Always use
<th>for header cells β never style a<td>to look like a header. - Add
scope="col"on column headers andscope="row"on row headers. - Use
<caption>to give the table a descriptive title. - For complex tables with multiple header rows, use the
headersattribute on data cells to point to the relevant header cell IDs.
<!-- Fully accessible table with id/headers association -->
<table>
<caption>Course pricing by plan</caption>
<thead>
<tr>
<th id="course" scope="col">Course</th>
<th id="free" scope="col">Free Plan</th>
<th id="pro" scope="col">Pro Plan</th>
</tr>
</thead>
<tbody>
<tr>
<th id="html-row" scope="row">HTML</th>
<td headers="free html-row">All lessons</td>
<td headers="pro html-row">All lessons + projects</td>
</tr>
<tr>
<th id="py-row" scope="row">Python</th>
<td headers="free py-row">First 10 lessons</td>
<td headers="pro py-row">Full course + mentoring</td>
</tr>
</tbody>
</table>
π Summary
- Core elements:
<table>,<tr>,<td>(data cell),<th>(header cell). - Semantic structure:
<thead>,<tbody>,<tfoot>divide the table into logical sections. <caption>gives the table a visible, accessible title β always include one.colspanmerges cells horizontally;rowspanmerges cells vertically.- Use
scope="col"/scope="row"on<th>elements for screen reader accessibility. - Tables are for tabular data only β never use them for page layout.
Frequently Asked Questions
Do I need thead and tbody on every table?
Technically no β they are optional. But using them is considered best practice. They improve code readability, enable independent CSS styling of sections, and help browsers handle long tables in print or overflow scenarios (some browsers keep <thead> visible at the top of each printed page).
How do I add borders to an HTML table?
Use CSS. The old HTML border attribute is deprecated. A modern approach:
table {
border-collapse: collapse; /* Remove double borders */
width: 100%;
}
th, td {
border: 1px solid #dee2e6;
padding: 0.5rem 0.75rem;
text-align: left;
}
thead tr {
background-color: #f8f9fa;
}
Can I make a table responsive?
Yes. Wrap the <table> in a <div> with overflow-x: auto β this lets the table scroll horizontally on small screens without breaking the page layout. For complex tables, consider a mobile-specific CSS approach that reformats rows as stacked cards on narrow screens.
What is the difference between td and th?
<td> is a standard data cell β it holds the actual data values. <th> is a header cell β it labels a row or column. Browsers render <th> bold and centred by default, but more importantly, screen readers announce it as a header, giving users context for the data cells it governs.