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>
Tables Are for Data β Structure Them So Everyone Can Read
A table is for tabular data (rows Γ columns of related values), not for page layout β use CSS Grid/Flexbox for layout. A properly built table also stays understandable to screen-reader users, who navigate cell by cell.
<table>
<caption>Q1 Sales by Region</caption>
<thead>
<tr><th scope="col">Region</th><th scope="col">Sales</th></tr>
</thead>
<tbody>
<tr><th scope="row">North</th><td>$12,000</td></tr>
</tbody>
</table>
scope and <caption> matter
scope="col"/scope="row" tells assistive tech which header describes a cell, so "$12,000" is announced as "North, Sales, $12,000" instead of a lone number. <caption> gives the whole table a title. These cost nothing visually and make data tables genuinely accessible.
ποΈ Practical Exercise
- Create a table with two columns and two rows.
- Add a header row using
<th>. - Group the header in
<thead>and the body in<tbody>. - Merge two cells using
colspan. - Add a
<caption>describing the table.
π₯ Challenge Exercise
Build a pricing table with three plans (columns) and four features (rows), using <thead> for the plan names and <tbody> for features. Use colspan for a "Most Popular" banner row across all columns, and add a <caption>. Confirm headers use <th> rather than bold text.
π 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.
Interview Questions
- What are the core elements used to build an HTML table?
- What is the difference between
<th>and<td>? - What do
<thead>,<tbody>, and<tfoot>provide? - What do
colspanandrowspando? - Why should tables be used for tabular data and not page layout?
Related Topics
FAQ
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).
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;
}
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.
<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.

