HTML Introduction to Table Styling
Every HTML table already looks like something the moment a browser parses it — even with zero attributes, the browser applies its own default styling. Before you can style a table well, you need to understand what the browser does automatically, which old HTML attributes used to control table appearance (and why you should avoid them now), and which structural elements exist purely to give a future stylesheet something to hook into. This lesson is the bridge between raw table markup and real table styling, which belongs to CSS.
Overview: How Tables Get Their Look
When a browser encounters a <table>, it builds a table box in the render tree using its built-in user-agent stylesheet — the default CSS every browser ships with. That stylesheet is why an unstyled table already has some structure: header cells (<th>) are bold and centered, the table uses a "separate borders" layout model with a small gap between cells, and text inside cells is vertically centered. None of this comes from your markup directly; it comes from rules like table { border-collapse: separate; border-spacing: 2px; } that every browser applies before your own CSS ever runs.
In the early days of the web (roughly 1995–2005), HTML itself carried presentational attributes for controlling this appearance: border, cellpadding, cellspacing, bgcolor, align, and width. Authors put visual instructions directly on the markup because CSS either didn't exist yet or wasn't reliably supported. Modern HTML has moved almost all visual control out of markup and into CSS, following the principle of separation of concerns: HTML describes structure and meaning, CSS describes appearance. Most of those attributes are now obsolete in the HTML specification — browsers still render them for backward compatibility with decades of old pages, but you should never write them in new markup.
What HTML markup does still control, correctly and permanently, is structure: how many logical column groups exist, which rows are headers versus body versus footer, and where a caption belongs. Elements like <caption>, <colgroup>, <col>, <thead>, <tbody>, and <tfoot> aren't styling tools themselves, but they are exactly what a CSS stylesheet later targets to apply styling efficiently — for example, styling an entire column via <colgroup> instead of repeating a class on every cell in that column.
Syntax: Old Attributes vs. Modern Structural Hooks
The table below summarizes the presentational attributes you will still see in old code (and should not write yourself), alongside the structural elements that replace their role.
| Old attribute / element | Status | Modern replacement |
|---|---|---|
border on <table> |
Obsolete presentational attribute | CSS border property |
cellpadding |
Obsolete presentational attribute | CSS padding on td/th |
cellspacing |
Obsolete presentational attribute | CSS border-spacing or border-collapse |
bgcolor |
Obsolete presentational attribute | CSS background-color |
align, valign |
Obsolete presentational attributes | CSS text-align, vertical-align |
width on table/col |
Obsolete presentational attribute | CSS width |
<caption> |
Valid, semantic | Still the correct way to title a table |
<colgroup> / <col> |
Valid, structural | Target whole columns from CSS via class |
<thead>/<tbody>/<tfoot> |
Valid, semantic | Group rows for CSS and for repeating headers when printing |
Examples
Example 1: Default rendering with no attributes at all
<table>
<tr>
<th>Fruit</th>
<th>Price</th>
</tr>
<tr>
<td>Apple</td>
<td>$1.20</td>
</tr>
<tr>
<td>Banana</td>
<td>$0.60</td>
</tr>
</table>
Result: The browser draws a three-row table. "Fruit" and "Price" appear bold and centered because they are <th> cells. The data rows below are left-aligned, ordinary weight text. There are no visible grid lines around the cells, and only a small gap separates each cell — this is the browser's default table appearance with zero styling applied.
This example proves the point from the Overview: a table always has some default look, driven entirely by the browser, before you write a single style rule.
Example 2: The old way — presentational attributes (for recognition only)
<table border="1">
<tr>
<th>Fruit</th>
<th>Price</th>
</tr>
<tr>
<td>Apple</td>
<td>$1.20</td>
</tr>
</table>
Result: Adding border="1" makes the browser draw a 1-pixel border around the outside of the table and around every individual cell, producing a visible grid. Because the default border model is "separate," you actually get a slightly doubled-looking line between adjacent cells rather than one shared border.
You will see border="1" constantly in old tutorials and legacy code, so it's worth recognizing what it does — but in new markup you should reach for CSS's border property instead, which gives you control over color, style, and the collapsed-vs-separate border model that this attribute can't offer.
Example 3: The modern way — structure that's ready for CSS
<table>
<caption>Quarterly Sales by Region</caption>
<colgroup>
<col class="region-col">
<col span="3" class="data-col">
</colgroup>
<thead>
<tr>
<th>Region</th>
<th>Q1</th>
<th>Q2</th>
<th>Q3</th>
</tr>
</thead>
<tbody>
<tr>
<td>North</td>
<td>120</td>
<td>135</td>
<td>150</td>
</tr>
<tr>
<td>South</td>
<td>98</td>
<td>110</td>
<td>115</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>218</td>
<td>245</td>
<td>265</td>
</tr>
</tfoot>
</table>
Result: The browser displays a caption reading "Quarterly Sales by Region" above the grid, then a bold header row (Region, Q1, Q2, Q3), two data rows, and a totals row at the bottom. Visually, without any CSS, the <colgroup> and its class attributes change nothing on screen — but they now exist as addressable hooks. A stylesheet could later select .data-col to right-align every number column at once, instead of repeating a class on eight separate <td> elements.
This is the core idea of modern table markup: describe the table's real structure precisely, and let CSS decide how it looks.
How It Works Step by Step
- The parser reads
<table>and creates a table box in the DOM; any direct<colgroup>/<col>children are recorded as column metadata before any rows are processed. <caption>, if present, must appear as the table's first child; the browser reserves space for it above the grid regardless of where you conceptually think of it.- Rows are grouped: explicit
<thead>,<tbody>, and<tfoot>sections are recorded as row groups. If you omit them, the browser silently wraps your loose<tr>elements in an implicit<tbody>in the DOM anyway. - Once the DOM tree exists, the browser applies its default user-agent stylesheet, producing the baseline look you saw in Example 1.
- Any author CSS (external, internal, or — historically — the deprecated attributes in this lesson) is applied on top, in the normal cascade order, overriding the defaults.
- Finally the layout engine computes column widths, row heights, and border rendering based on the finished box tree — which is why the same markup can look different depending on which stylesheet, if any, is loaded.
Common Mistakes
Mistake 1: Mixing presentational attributes into the markup
<table border="1" cellpadding="8" cellspacing="0" bgcolor="#f2f2f2" width="100%">
<tr>
<td align="center">Name</td>
<td align="center">Score</td>
</tr>
</table>
This still renders, but it mixes five obsolete visual attributes into the structure, forces you to repeat align on every single cell, and gives you no way to respond to screen size or theme changes. It also uses <td> where a header cell (<th>) would be more meaningful. Write clean structural markup instead and leave every visual decision to CSS:
<table class="score-table">
<thead>
<tr>
<th>Name</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grace Hopper</td>
<td>98</td>
</tr>
</tbody>
</table>
Mistake 2: Using <col> outside <colgroup>, and giving it content
<table>
<col span="2">
<col>This column holds totals</col>
</col>
<tr>
<td>1</td>
<td>2</td>
</tr>
</table>
Two errors here: <col> is only valid as a direct child of <colgroup>, and <col> is a void element — it can never wrap text or a closing tag. The corrected version wraps the columns properly and drops the invalid content:
<table>
<colgroup>
<col span="2" class="data-col">
</colgroup>
<tr>
<td>1</td>
<td>2</td>
</tr>
</table>
Best Practices
- Never write
border,cellpadding,cellspacing,bgcolor,align, orwidthin new HTML — style tables entirely with CSS. - Always add a
<caption>when a table needs a title; it's announced by screen readers and is more reliable than a plain paragraph above the table. - Use
<thead>,<tbody>, and<tfoot>even on simple tables — they make your intent explicit and let browsers repeat headers when a long table is printed. - Reach for
<colgroup>/<col>with aclasswhen an entire column needs distinct styling, rather than repeating a class on every cell in that column. - Use
<th>for header cells, not bold<td>— this is a structural signal for assistive technology, not just a font weight. - Keep any styling attributes you do need (like a hook for CSS) limited to
classorid; let the stylesheet, not the markup, decide colors, spacing, and borders.
Practice Exercises
- Write a table of three programming languages and their release years with no attributes at all. Predict, then check, which parts render bold and which render left-aligned by default.
- Take your table from the previous exercise and add a
<caption>, wrap the header row in<thead>, and wrap the data rows in<tbody>. - Build a table with four columns where the first column should visually differ from the other three once CSS is added. Use
<colgroup>and<col>with two differentclassvalues to prepare it for that future styling, without adding any presentational attributes.
Summary
- Browsers apply default styling to every table automatically, even with no attributes present.
- Old presentational attributes (
border,cellpadding,cellspacing,bgcolor,align,width) are obsolete and should be replaced by CSS. <caption>,<colgroup>/<col>, and<thead>/<tbody>/<tfoot>are structural, not visual — they exist so CSS can target the table efficiently.<col>is a void element and must live inside<colgroup>.- Actual visual styling — borders, colors, padding, alignment — is the job of the CSS course, not HTML.
