Table Attributes in HTML
Learn to merge table cells in HTML using colspan and rowspan, build complex HTML tables, and improve accessibility with the scope and headers attributes, explained with real examples.
If you’ve been following this HTML tables series with us, you’re already ahead of most beginners. In Building Tables in HTML, we covered the core structure: table, tr, th, and td. Then in Table Headers, Body, and Footer Elements, we organized everything into thead, tbody, and tfoot for a properly structured, semantic table.
Both of those gave you a clean, working table. But here’s the thing: most real-world data doesn’t fit neatly into a simple grid of equal-sized cells.
Think about a class schedule where “Monday” needs to span three different time slots. Or a pricing table where a “Most Popular” badge needs to stretch across two columns. Or a financial report where a row header applies to an entire section of data. These are the situations where the basic table structure starts to feel limiting.
That’s exactly what this article is about. HTML table attributes, specifically colspan, rowspan, scope, and headers, are what give you full control over how your table cells behave, look, and communicate with assistive technology.
Let’s go through each one carefully, with real examples you can actually use.
01What “Merging Cells” Actually Means
Before we touch any code, let’s build the mental model first. It makes everything much easier.
A standard HTML table is a grid. Rows go horizontally, columns go vertically. Every cell sits at the intersection of one row and one column.
When you “merge” cells, you’re telling one cell to occupy the space of multiple cells in that grid. You can merge horizontally (across columns) or vertically (down rows), or both at the same time.
Here’s a visual that shows the difference between a regular table and one with merged cells:
Regular Table vs Merged Cells Table
| Mon | Tue | Wed |
|---|---|---|
| Math | Math | Math |
| 9 AM |
| Mon · Tue · Wed | ||
|---|---|---|
| Math (All Three Days) | ||
| 9 AM | Science | English |
| History | Art | |
The left table is flat and repetitive. The right one uses merged cells to eliminate redundancy and tell the story more clearly. The purple cells use colspan (horizontal merge), the blue cell uses rowspan (vertical merge).
Now let’s look at how each attribute works.
02Merging Cells Horizontally: The colspan Attribute
The colspan attribute tells a cell to stretch across a given number of columns. You put it on either a th or td element, and give it a number as its value.
<table>
<tr>
<th colspan="3">Full Name</th>
</tr>
<tr>
<td>First</td>
<td>Middle</td>
<td>Last</td>
</tr>
</table>
The header “Full Name” has colspan="3", which means it occupies the space of three columns. The row below it has three regular cells that each sit under one-third of that merged header.
Something worth noting: when you use colspan="3" on a cell, you need to remove the other cells from that row. If you had 3 columns and used colspan="3" on one cell, you don’t add 2 more cells to that row. The merged cell is already taking up that space.
The rule is simple: if a row has 4 columns and you use colspan="2" on one cell, that row should only have 3 cells total (1 merged + 2 regular = 4 column widths).
Here’s a more practical use case: a product table with a “Specifications” header that spans three sub-columns.
<table>
<thead>
<tr>
<th rowspan="2">Product</th>
<th colspan="3">Specifications</th>
<th rowspan="2">Price</th>
</tr>
<tr>
<th>Weight</th>
<th>Color</th>
<th>Size</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget A</td>
<td>120g</td>
<td>Blue</td>
<td>Medium</td>
<td>$12.99</td>
</tr>
</tbody>
</table>
Notice that the “Product” and “Price” headers use rowspan="2" to span both header rows. We’ll get to rowspan in the next section, but this code gives you a preview of how they work together in a two-row header structure.
03Merging Cells Vertically: The rowspan Attribute
Where colspan goes sideways, rowspan goes downward. It tells a cell to stretch across multiple rows.
<table>
<tr>
<th rowspan="3">Monday</th>
<td>9:00 AM - Math</td>
</tr>
<tr>
<td>10:00 AM - Science</td>
</tr>
<tr>
<td>11:00 AM - English</td>
</tr>
</table>
The “Monday” cell has rowspan="3", so it occupies the space of 3 rows in the first column. Each of the three rows only has one td because the first column is already filled by the spanning cell.
This is where beginners often get confused, so let’s be clear: when a cell uses rowspan="3", the rows below it must skip that column. You don’t add an empty td for the rows underneath. The merged cell is already there.
Schedule Table Using rowspan
| Day | Time Slot | Subject |
|---|---|---|
| Monday rowspan=”3″ |
9:00 AM | Math |
| 10:00 AM | Science | |
| 11:00 AM | English | |
| Tuesday rowspan=”2″ |
9:00 AM | History |
| 10:00 AM | Art |
Cells using rowspan
Regular cells (column 1 is skipped in these rows)
That blue “Monday” cell sits in rows 1, 2, and 3 of its column simultaneously. The browser knows to skip the first column in those rows because the rowspan already covers it.
04Using HTML Colspan and Rowspan Together
Now here’s where things get genuinely interesting. You can apply both colspan and rowspan to the same cell. That cell will then occupy a rectangular block of the table grid.
<table>
<thead>
<tr>
<th rowspan="2">Name</th>
<th colspan="2">Q1 Results</th>
<th colspan="2">Q2 Results</th>
</tr>
<tr>
<th>Sales</th>
<th>Revenue</th>
<th>Sales</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sarah</td>
<td>142</td>
<td>$8,400</td>
<td>160</td>
<td>$9,200</td>
</tr>
<tr>
<td>Ahmed</td>
<td>98</td>
<td>$5,700</td>
<td>115</td>
<td>$6,100</td>
</tr>
</tbody>
</table>
The “Name” header uses rowspan="2" because the heading section is two rows tall. “Q1 Results” and “Q2 Results” each use colspan="2" because each has two sub-columns beneath them.
This kind of two-tier header is one of the most common patterns in complex HTML tables. You’ll see it in financial reports, sports stats, product comparisons, and a dozen other places.
Sales Report: Two-Tier Header Using colspan and rowspan
| Name rowspan=”2″ |
Q1 Results colspan=”2″ |
Q2 Results colspan=”2″ |
||
|---|---|---|---|---|
| Sales | Revenue | Sales | Revenue | |
| Sarah | 142 | $8,400 | 160 | $9,200 |
| Ahmed | 98 | $5,700 | 115 | $6,100 |
colspan + rowspan on same cell
colspan=”2″
Regular headers
05Building Complex HTML Tables: A Real-World Schedule
Let’s build a real weekly schedule table from scratch. This is the kind of thing you’d actually build for a school, gym, or event website. It uses both colspan and rowspan in a meaningful way.
<table>
<caption>Weekly Class Schedule</caption>
<thead>
<tr>
<th>Time</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
</tr>
</thead>
<tbody>
<tr>
<th>9:00 AM</th>
<td colspan="2">Mathematics (Hall A)</td>
<td>Physics</td>
<td colspan="2">Chemistry Lab</td>
</tr>
<tr>
<th>10:00 AM</th>
<td>History</td>
<td rowspan="2">Project Work (Double Period)</td>
<td>English</td>
<td>Art</td>
<td>Computer Lab</td>
</tr>
<tr>
<th>11:00 AM</th>
<td>Geography</td>
<td>Music</td>
<td colspan="2">Sports and PE</td>
</tr>
</tbody>
</table>
Notice what’s happening here: “Mathematics” spans Monday and Tuesday with colspan="2", “Project Work” spans the 10 AM and 11 AM rows on Tuesday with rowspan="2", and “Sports and PE” spans Thursday and Friday on the bottom row. This is a genuinely complex table built with just three HTML attributes.
Watch your cell count: when using rowspan, the rows below must have fewer cells in that column. If you forget to remove the “skipped” cell, your table layout will break visually. Always count the effective column widths per row when debugging merge issues.
06The scope Attribute: Your Table’s Accessibility Foundation
This is the one most beginners skip, and it’s a real shame. The scope attribute on a th element tells screen readers exactly which cells that header belongs to.
Without scope, a screen reader reading a complex table might not know whether a header cell describes the column below it, the row beside it, or something else. It has to guess. The scope attribute removes the guesswork.
There are four valid values:
- col : this header describes the cells in its column
- row : this header describes the cells in its row
- colgroup : this header describes a group of columns (used with
colspan) - rowgroup : this header describes a group of rows (used with
thead,tbody,tfoot)
For most tables, you’ll mainly use col and row.
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Department</th>
<th scope="col">Salary</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Fatima Khan</th>
<td>Engineering</td>
<td>$95,000</td>
</tr>
<tr>
<th scope="row">James Lee</th>
<td>Marketing</td>
<td>$78,000</td>
</tr>
</tbody>
</table>
The top headers have scope="col" because they label columns. The row headers in the first column have scope="row" because they label a specific person’s row of data.
When a screen reader encounters this table, it can now announce: “Name, column header” and then “Fatima Khan, row header. Engineering. $95,000.” That’s a completely different experience from a table with no scope attributes.
Hover over a header to see which cells it “owns”
| Name scope=”col” |
Dept scope=”col” |
Salary scope=”col” |
|
|---|---|---|---|
| Row 1 scope=”row” |
Fatima Khan | Engineering | $95,000 |
| Row 2 scope=”row” |
James Lee | Marketing | $78,000 |
| Row 3 scope=”row” |
Aisha Patel | Design | $82,000 |
👆 Hover over the column headers (cyan) or row headers (pink) to see which cells they cover.
scope=”col” highlights its column
scope=”row” highlights its row
When you have a merged header using colspan, use scope="colgroup" on it to indicate it covers a group of columns. Pair that with scope="col" on the individual sub-headers below.
<thead>
<tr>
<th rowspan="2" scope="col">Name</th>
<th colspan="2" scope="colgroup">Q1 Results</th>
<th colspan="2" scope="colgroup">Q2 Results</th>
</tr>
<tr>
<th scope="col">Sales</th>
<th scope="col">Revenue</th>
<th scope="col">Sales</th>
<th scope="col">Revenue</th>
</tr>
</thead>
The “Q1 Results” header now correctly describes that it covers a group of two columns, not just one.
07The headers Attribute: When scope Isn’t Enough for Screen Readers
For simple and moderately complex tables, scope is sufficient. But some tables are genuinely complex: multiple header levels, cells that belong to more than one header, or irregular structures where the relationship between headers and data cells isn’t clear from position alone.
The headers attribute handles those cases. It creates an explicit link between a data cell and one or more header cells by referencing their id values.
<table>
<thead>
<tr>
<th id="h-name">Name</th>
<th id="h-q1" colspan="2">Q1</th>
<th id="h-q2" colspan="2">Q2</th>
</tr>
<tr>
<td></td>
<th id="h-q1-sales">Sales</th>
<th id="h-q1-rev">Revenue</th>
<th id="h-q2-sales">Sales</th>
<th id="h-q2-rev">Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="h-name">Sarah</td>
<td headers="h-q1 h-q1-sales">142</td>
<td headers="h-q1 h-q1-rev">$8,400</td>
<td headers="h-q2 h-q2-sales">160</td>
<td headers="h-q2 h-q2-rev">$9,200</td>
</tr>
</tbody>
</table>
Look at the cell that contains “142”. Its headers value is "h-q1 h-q1-sales". That tells a screen reader: this cell belongs to both the “Q1” group header AND the “Sales” sub-header. So it will announce it as: “Q1, Sales: 142.” That’s exactly what a sighted user understands by looking at the table visually.
The headers attribute creates explicit ID-based links between cells
| Nameid=”ex-name” | Q1 Resultsid=”ex-q1″ | |
|---|---|---|
| Salesid=”ex-sales” | Revenueid=”ex-rev” | |
| Sarah↑ ex-name | 142↑ ex-q1 + ex-sales$8,400↑ ex-q1 + ex-rev | |
The cyan labels show which header IDs each data cell references via the headers attribute.
To be honest, you won’t always need the headers attribute. A well-structured table with good scope values handles most situations. But for genuinely complex multi-level tables like financial statements, academic schedules, or data reports, headers is the right tool to reach for.
08The colgroup and col Attributes: Working by Column
There’s one more set of HTML table attributes worth knowing: colgroup and col. These aren’t about merging cells. They’re about grouping and targeting entire columns so you can apply consistent styling to them.
The key attribute here is span, used on both elements to indicate how many columns a group or column rule applies to.
<table>
<colgroup>
<col span="1"> <!-- 1st column: Name -->
<col span="2" class="highlight"> <!-- 2nd + 3rd columns: Q1 data -->
<col span="2"> <!-- 4th + 5th columns: Q2 data -->
</colgroup>
<thead>
<tr>
<th>Name</th>
<th colspan="2">Q1</th>
<th colspan="2">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ali</td>
<td>120</td>
<td>$6,000</td>
<td>135</td>
<td>$7,200</td>
</tr>
</tbody>
</table>
<style>
col.highlight { background-color: #f0f4ff; }
</style>
The span="2" on the second col element means that the styling applies to two consecutive columns (columns 2 and 3). Without span, you’d need one col element per column.
Note on CSS and col: col and colgroup only accept a limited set of CSS properties, mainly background, border, width, and visibility. For full styling control, you’ll need to target cells by class directly. CSS will be covered in full detail in a later series.
You can also use colgroup with scope="colgroup" on the header to link grouped headers to grouped columns. This creates a very precise structure that both browsers and screen readers can interpret correctly.
09Things That Trip People Up
Let’s be real about the common mistakes, because these are easy to run into even when you understand the concepts.
Forgetting to remove cells after using rowspan. If you use rowspan="3" in the first cell of a row, the next two rows must not have a cell in that column position. People often add an empty td out of habit, which breaks the layout.
Getting the column count wrong. This one’s sneaky. If you have 5 columns and use colspan="3" on one cell, that row should only have 3 cells total (the colspan cell counts as 3). Miscounting causes cells to overflow or wrap unexpectedly.
Using both colspan and rowspan without planning on paper first. Seriously, just sketch it out on paper or a spreadsheet before writing code. Complex merged tables are much easier to build when you can see the grid before you start.
Adding scope on td instead of th. The scope attribute belongs on th elements only. Putting it on a regular data cell (td) has no semantic effect.
Mixing up the id and headers values. When using the headers attribute, make sure the IDs actually exist and are spelled exactly the same. A typo in an ID reference silently breaks the accessibility link without any visible error.
10A Complete Real-World Example: Invoice Table
Let’s bring everything together. Here’s a styled invoice table that uses colspan, rowspan, scope, and proper semantic structure all in one place.
<table>
<caption>Invoice #1042 - April 2026</caption>
<colgroup>
<col span="1">
<col span="1">
<col span="1">
<col span="2" class="totals-cols">
</colgroup>
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Qty</th>
<th scope="col">Unit Price</th>
<th scope="colgroup" colspan="2">Totals</th>
</tr>
<tr>
<th colspan="3"></th>
<th scope="col">Subtotal</th>
<th scope="col">Tax (10%)</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Web Design</th>
<td>1</td>
<td>$1,200</td>
<td>$1,200</td>
<td>$120</td>
</tr>
<tr>
<th scope="row">Logo Design</th>
<td>1</td>
<td>$400</td>
<td>$400</td>
<td>$40</td>
</tr>
<tr>
<th scope="row">SEO Setup</th>
<td>3</td>
<td>$150</td>
<td>$450</td>
<td>$45</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row" colspan="3">Total Due</th>
<td colspan="2">$2,255 (incl. tax)</td>
</tr>
</tfoot>
</table>
This table has two-level headers, column groups, row headers, colspan on the footer for the total row, and proper scope values throughout. It’s the kind of structure you’d actually ship on a real project.
Here’s what it looks like rendered, styled with the full set of attributes in place:
Invoice Table: All Attributes in Action
| Item | Qty | Unit Price | Totals colspan=”2″ scope=”colgroup” | |
|---|---|---|---|---|
| Subtotal | Tax (10%) | |||
| Web Design | 1 | $1,200 | $1,200 | $120 |
| Logo Design | 1 | $400 | $400 | $40 |
| SEO Setup | 3 | $150 | $450 | $45 |
| Total Due colspan=”3″ scope=”row” | $2,255 colspan=”2″ | |||
colspan in use
scope=”row” headers
colspan + scope together
11Your Attribute Reference Card
colspan
Stretches a cell horizontally across multiple columns.
colspan="3"
Goes on th or td. Remove the extra cells in that row.
rowspan
Stretches a cell vertically across multiple rows.
rowspan="2"
Goes on th or td. Skip that column position in subsequent rows.
scope
Tells screen readers which cells a header covers.
scope="col" scope="row"
scope="colgroup" scope="rowgroup"
Goes on th only. Essential for accessibility.
headers
Links a data cell to one or more header cells by ID reference.
headers="h-q1 h-sales"
Goes on td. The header th elements must have matching id values.
span (on col)
Tells a col element how many columns its styling applies to.
<col span="2" class="highlight">
Without span, each col covers exactly one column.
scope=”colgroup”
Use on a header that spans multiple columns via colspan.
<th colspan="2" scope="colgroup">
Pairs with scope="col" on the sub-headers below.
12Take This With You
You’ve just learned the attributes that transform a basic HTML table into a genuinely professional, accessible data structure. colspan and rowspan give you control over the visual shape of your table. scope and headers give screen readers and assistive technology the context they need to read it correctly.
What’s worth remembering is that these attributes aren’t just for “advanced” tables. Even a simple two-column table with row headers benefits from scope="row". Getting into that habit early will serve you well throughout your career.
The next article in this series covers table captions and summary information, specifically the caption element. You already saw it appear briefly in the invoice example above, and it has more impact on both SEO and accessibility than most people realize.
Build a table today using what you learned. Start with something simple: a pricing table, a class schedule, or a product comparison. Apply colspan where it makes the data clearer, add scope to every header, and see how much better your HTML reads, both for humans and machines.
Table Attributes in HTML
Loading challenge…
Ready to Practice?
Select a difficulty level above to load your challenge.