HTML Guides for colspan
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
An empty colspan on a <td> is invalid, because the attribute takes the number of columns the cell spans, a whole number from 1 to 1000.
Both colspan="" and a bare colspan with no value get this error. A value made only of spaces, like colspan=" ", gets a different one that starts with "Expected a digit".
Empty attributes like this usually come from a table builder in a CMS, or from a template that prints colspan="{{ cell.colspan }}" on every cell, whether or not the cell spans anything. The same templates often print rowspan="" too, which gets its own error saying "non-negative integer" instead, because rowspan also accepts 0.
Browsers treat an empty colspan as 1, the same as leaving the attribute out, so the table looks the same once you remove it. Change the template to print colspan only when the cell spans two or more columns.
Invalid example
<table>
<tr>
<th>Plan</th>
<th>Price</th>
</tr>
<tr>
<td colspan="">Basic</td>
<td colspan="">$10</td>
</tr>
<tr>
<td colspan="2">Prices include tax.</td>
</tr>
</table>
Valid example
<table>
<tr>
<th>Plan</th>
<th>Price</th>
</tr>
<tr>
<td>Basic</td>
<td>$10</td>
</tr>
<tr>
<td colspan="2">Prices include tax.</td>
</tr>
</table>
A colspan on a <th> has to say how many columns the header covers, as a whole number from 1 to 1000, so colspan="" is invalid.
Writing colspan with no value at all gives the same message. If the value holds only spaces, as in colspan=" ", the validator reports it differently, with a message that begins "Expected a digit".
Header cells often end up like this when a table plugin or theme writes the span attributes on every <th> and fills them from a setting that is blank for most columns. Check the same headers for rowspan="". The validator flags it separately, as "not a valid non-negative integer", because rowspan also accepts 0.
An empty colspan changes nothing on screen: browsers read it as 1, just as they do when the attribute is missing. Remove it from every header that sits over a single column, and keep a number only where a header groups several columns, such as colspan="2" above two related columns.
Invalid example
<table>
<tr>
<th colspan="">Course</th>
<th colspan="2">Schedule</th>
</tr>
<tr>
<td>Biology</td>
<td>Monday</td>
<td>Wednesday</td>
</tr>
</table>
Valid example
<table>
<tr>
<th>Course</th>
<th colspan="2">Schedule</th>
</tr>
<tr>
<td>Biology</td>
<td>Monday</td>
<td>Wednesday</td>
</tr>
</table>
The colspan attribute of a <td> takes digits only, so a single space after the number, as in colspan="2 ", makes the value invalid.
The space usually comes from a template. In <td colspan="{{ span }} "> or <td colspan="<?php echo $span; ?> ">, the space before the closing quote ends up in the attribute, right after the number. A helper that pads numbers to a fixed width has the same effect. A space before the number (" 2") or a line break inside the quotes gets the same error. The character after "saw" is the first one that isn't a digit: a blank here, p in 2px, % in 50%. A value with markup in it, like 2><span class=, means a closing quote is missing.
The HTML standard requires colspan to be a valid non-negative integer greater than zero and no larger than 1000, which is one or more digits with nothing around them. Browsers skip leading spaces, read the digits and ignore the rest, so "2 " still spans two columns. The validator's table check counts a cell with a trailing space as one column, though, so a "table row was N columns wide" warning often appears alongside this error.
Fix the template so the closing quote comes right after the number, or trim the value before printing it.
Invalid example
<table>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Price</th>
</tr>
<tr>
<td>Notebook</td>
<td>2</td>
<td>$8</td>
</tr>
<tr>
<td colspan="2 ">Total</td>
<td>$8</td>
</tr>
</table>
Valid example
<table>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Price</th>
</tr>
<tr>
<td>Notebook</td>
<td>2</td>
<td>$8</td>
</tr>
<tr>
<td colspan="2">Total</td>
<td>$8</td>
</tr>
</table>
The colspan attribute of a <th> takes a positive whole number written with digits only, and the validator names the first character in the value that isn't a digit.
colspan sets how many columns a header cell spans. The HTML standard requires a valid non-negative integer greater than zero and no larger than 1000, such as 2 or 3. Units, percentages, decimals, signs, spaces, quotes and keywords like auto all make the value invalid. The character after "saw" is where the number stopped: p in 2px, % in 100%, . in 1.5, - in -1.
A value that contains markup, like 2><span class=, usually means a quote is missing. In <th colspan="2><span class="label">, nothing closes the quote after 2, so the parser reads on to the next " and puts everything in between into colspan. The <span> start tag is lost, and what's left of it becomes stray attributes on the <th>. That's why errors like "No space between attributes" and "Quote in attribute name" usually appear next to this one. When the number is missing too, the value starts right at the >. A CMS or rich text editor can store the broken attribute with the < escaped as <, so check the saved content as well as the template.
Browsers read the leading digits and ignore the rest, so 2px still spans two columns. A value that doesn't start with a digit, or is zero, falls back to 1. The header then covers one column and every cell after it in the row moves over, so screen readers can announce data cells under the wrong header.
Write the number on its own, without a unit, and close the attribute's quote before the >.
Invalid example
<table>
<tr>
<th colspan="2px">Name</th>
<th colspan="2><span class="label">Contact</span></th>
</tr>
<tr>
<td>Ada</td>
<td>Lovelace</td>
<td>ada@example.com</td>
<td>555-0100</td>
</tr>
</table>
Valid example
<table>
<tr>
<th colspan="2">Name</th>
<th colspan="2"><span class="label">Contact</span></th>
</tr>
<tr>
<td>Ada</td>
<td>Lovelace</td>
<td>ada@example.com</td>
<td>555-0100</td>
</tr>
</table>
The <col> elements in this table declare more columns than the rows use, and no <td> or <th> begins in the reported range.
Each <col> inside a <colgroup> adds one column to the table, or as many columns as its span attribute says, so <col span="4"> declares four. The HTML table model requires every column to have at least one cell that starts in it. A cell that only crosses a column through colspan doesn't count. With <col span="4"> and rows made of <th colspan="4">, or of <td> followed by <td colspan="3">, cells start only in columns 1 and 2, and the validator reports columns 3 and 4 together as the range 3…4.
This often happens in layout tables that declare a fine grid in the <colgroup> and then merge those columns with colspan in every row. It also happens when the rows are shorter than the column markup, which adds a second error: "A table row was 2 columns wide, which is less than the column count established using column markup (4)". When only one column is empty, the validator names that single column ("Table column 4") instead of a range.
To fix it, declare only the columns the rows use. Remove <col> elements or lower their span, and reduce the colspan values to match. Deleting the <colgroup> alone isn't enough: the oversized colspan then creates the empty columns itself, and the validator reports them as established by th or td instead.
Invalid example
The <col> declares four columns, but cells start only in the first two:
<table>
<colgroup>
<col span="4">
</colgroup>
<tr>
<th colspan="4">Opening hours</th>
</tr>
<tr>
<td>Monday</td>
<td colspan="3">9:00 to 17:00</td>
</tr>
</table>
Valid example
Two columns in the <colgroup>, and a cell starts in each of them:
<table>
<colgroup>
<col class="day">
<col class="hours">
</colgroup>
<tr>
<th colspan="2">Opening hours</th>
</tr>
<tr>
<td>Monday</td>
<td>9:00 to 17:00</td>
</tr>
</table>
A <th> with a colspan wider than the rest of the table adds the columns in the reported range, and no <td> or <th> begins in any of them.
The validator builds the table grid from each row's cells and their colspan values. A <th colspan="4"> that starts in column 2 covers columns 2 to 5, so the table has at least five columns. The HTML table model requires every column to have at least one cell that starts in it, and a cell that only crosses a column through colspan doesn't count. If the other rows have only two cells, nothing starts in columns 3 to 5, and the validator reports them as the range 3…5, naming th as the element that created them.
This often happens with header rows that group headings over more columns than the body has, or with a colspan left over from a wider version of the table. The short rows usually add a warning too: "A table row was 2 columns wide, which is less than the column count established by the first row (5)." When only one column is empty, the validator names that single column ("Table column 3") instead of a range.
To fix it, lower the colspan so the header covers only the columns the rows use. If the table really needs the extra columns, add cells to the other rows so a cell starts in each of them.
Invalid example
The second heading spans four columns, but each body row has only two cells:
<table>
<tr>
<th>Term</th>
<th colspan="4">Dates</th>
</tr>
<tr>
<td>Autumn</td>
<td>September to December</td>
</tr>
</table>
Valid example
Without the colspan, the header and the body both have two columns:
<table>
<tr>
<th>Term</th>
<th>Dates</th>
</tr>
<tr>
<td>Autumn</td>
<td>September to December</td>
</tr>
</table>
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries