HTML Table Styling
Table Styling Overview
An unstyled HTML table is functional but honestly not great to look at - no borders between cells by default, inconsistent spacing, header cells that look almost the same as data cells - and the gap between a bare table and a table that actually looks professional is mostly just a few CSS rules. This guide goes through both the old HTML attribute approach (because you will encounter it in older code) and the CSS approach that you should use for anything you build today. By the end you'll know how to add borders, control spacing, stripe rows for readability, handle hover states, and make tables that don't fall apart on a phone screen.
Styling Approaches
HTML Attributes- The old approach - attributes like border, cellpadding, and bgcolor written directly on the table element. Deprecated in HTML5 but still works in browsers and still shows up in older codebases.CSS- The current approach - all styling in a stylesheet, full control over every visual property, much easier to maintain and update.Responsive Design- Techniques for making tables that work on small screens, where wide tables are one of the more common layout problems beginners run into.
Traditional HTML Styling Attributes
Before CSS became the standard way to style everything, table appearance was controlled with attributes written directly on the HTML elements - border for the border thickness, cellpadding for the space inside cells, cellspacing for the gap between cells, bgcolor for background color, and align for text alignment. These are all deprecated now which means they're not valid HTML5 and you shouldn't write them in new code, but browsers still support them because the web has a strong backward compatibility principle and breaking old pages is considered worse than carrying legacy features indefinitely. You'll encounter these attributes in older HTML templates, CMS themes, and email HTML - yes email, because HTML email essentially still lives in the early 2000s for compatibility reasons and bgcolor on table cells is still a legitimate email technique.
1<!-- Attribute-based styling - works but deprecated -->
2<table border="1" cellpadding="5" cellspacing="0" width="100%">
3 <tr>
4 <th bgcolor="#f2f2f2">Product</th>
5 <th bgcolor="#f2f2f2">Price</th>
6 <th bgcolor="#f2f2f2">In Stock</th>
7 </tr>
8 <tr>
9 <td>Widget A</td>
10 <td align="right">$12.99</td>
11 <td align="center">Yes</td>
12 </tr>
13 <tr bgcolor="#f9f9f9">
14 <td>Widget B</td>
15 <td align="right">$24.99</td>
16 <td align="center">No</td>
17 </tr>
18</table>Common HTML Table Attributes
border- Sets the border width in pixels around and between cells. border="1" gives you a thin border, higher numbers make it thicker.cellpadding- Sets the space between cell content and the cell's border - what CSS padding does, but as an HTML attribute applied to the whole table.cellspacing- Sets the gap between cells - what CSS border-spacing does. Setting it to 0 removes the double-border gap you get by default.width- Sets table width in pixels or percentage. width="100%" makes it span its container.bgcolor- Sets background color on a table, tr, td, or th element. Still used in HTML email.align- Sets horizontal text alignment within a cell. Use CSS text-align in new code instead.
Basic CSS Table Styling
The difference between an unstyled table and one with even just five or six CSS declarations is significant - border-collapse alone changes how the borders look completely, and adding some padding to the cells makes everything feel less cramped. The basic CSS table pattern that I use as a starting point for almost every project is: collapse the borders, add padding to th and td, give the header a background color, and add alternating row colors with nth-child. Those four things get you 80% of the way to a table that looks intentional.
1<style>
2 .basic-table {
3 width: 100%;
4 border-collapse: collapse;
5 font-family: Arial, sans-serif;
6 margin: 1em 0;
7 }
8
9 .basic-table th, .basic-table td {
10 border: 1px solid #ddd;
11 padding: 8px;
12 text-align: left;
13 }
14
15 .basic-table th {
16 background-color: #f2f2f2;
17 font-weight: bold;
18 }
19
20 .basic-table tr:nth-child(even) {
21 background-color: #f9f9f9;
22 }
23
24 .basic-table tr:hover {
25 background-color: #e6f7ff;
26 }
27</style>
28
29<table class="basic-table">
30 <tr>
31 <th>Product</th>
32 <th>Price</th>
33 <th>In Stock</th>
34 </tr>
35 <tr>
36 <td>Widget A</td>
37 <td>$12.99</td>
38 <td>Yes</td>
39 </tr>
40 <tr>
41 <td>Widget B</td>
42 <td>$24.99</td>
43 <td>No</td>
44 </tr>
45</table>Key CSS Properties
border-collapse: collapse- The most important table CSS property - merges adjacent cell borders into one. Without it you get double borders with a gap between them which looks wrong on every table.padding on th and td- Controls the space between cell content and the cell border. The default is usually too tight - 8px to 12px is a common starting point.tr:nth-child(even)- Selects every other row for zebra striping. Use :nth-child(odd) for the opposite pattern. Scope it to tbody if you don't want the header row counted.tr:hover- Highlights the row the user is currently over - useful for wide tables where tracking which row you're reading can be difficult.
Advanced CSS Table Styling
Once the basics are in place you can layer in more polish - rounded corners, box shadows, uppercase headers, color-coded status cells - and the difference between a basic styled table and a properly designed one is mostly just these details stacked up. The one gotcha with rounded corners on tables is that border-collapse: collapse and border-radius don't play nicely together, so you need to switch to border-collapse: separate with border-spacing: 0 and add overflow: hidden to the table element to clip the corners. I kept hitting this problem and adding border-radius and seeing nothing happen before I looked up why.
1<style>
2 .advanced-table {
3 width: 100%;
4 border-collapse: separate;
5 border-spacing: 0;
6 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
7 box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
8 border-radius: 6px;
9 overflow: hidden;
10 }
11
12 .advanced-table th, .advanced-table td {
13 padding: 12px 15px;
14 text-align: left;
15 border-bottom: 1px solid #e1e1e1;
16 }
17
18 .advanced-table th {
19 background-color: #4CAF50;
20 color: white;
21 font-weight: 600;
22 text-transform: uppercase;
23 letter-spacing: 0.5px;
24 }
25
26 .advanced-table tr:last-child td {
27 border-bottom: none;
28 }
29
30 .advanced-table tr:nth-child(even) {
31 background-color: #f8f8f8;
32 }
33
34 .advanced-table tr:hover {
35 background-color: #f1f1f1;
36 transition: background-color 0.2s ease;
37 }
38
39 .price {
40 text-align: right;
41 font-family: monospace;
42 font-weight: bold;
43 color: #2c3e50;
44 }
45
46 .in-stock {
47 color: #27ae60;
48 font-weight: bold;
49 }
50
51 .out-of-stock {
52 color: #e74c3c;
53 font-weight: bold;
54 }
55</style>
56
57<table class="advanced-table">
58 <thead>
59 <tr>
60 <th>Product</th>
61 <th>Description</th>
62 <th>Price</th>
63 <th>Availability</th>
64 </tr>
65 </thead>
66 <tbody>
67 <tr>
68 <td>Widget A</td>
69 <td>Basic model with standard features</td>
70 <td class="price">$12.99</td>
71 <td class="in-stock">In Stock</td>
72 </tr>
73 <tr>
74 <td>Widget B</td>
75 <td>Premium model with advanced capabilities</td>
76 <td class="price">$24.99</td>
77 <td class="out-of-stock">Backordered</td>
78 </tr>
79 </tbody>
80</table>Advanced Techniques
box-shadow- Adds a subtle shadow around the table that lifts it off the page visually. A small, soft shadow looks more professional than a hard border around the whole table.border-radius- Rounds the table corners. Requires border-collapse: separate and overflow: hidden on the table element to actually work - won't do anything with border-collapse: collapse.text-transform: uppercase- A common pattern for column header labels - makes them feel more like UI labels and less like document headings.transition- Animates the hover background change so it fades in smoothly instead of snapping. transition: background-color 0.2s ease is the standard value.
Responsive Tables for Mobile
Tables are one of the harder HTML elements to make work on small screens because they have intrinsic width requirements - a table with six columns needs a certain amount of horizontal space and there's no easy way to make that work on a phone screen the way you'd reflow text. There are two main approaches and they suit different situations. The simple one is the scrollable wrapper: wrap the table in a div with overflow-x: auto and the table keeps its full structure while users can scroll horizontally. The more complex one is the stacking approach where at small screen sizes you switch each row into a card-style block using display: block, hide the header row, and use data-label attributes combined with CSS ::before pseudo-elements to show the column names as labels next to each value - it takes more setup but looks much better on narrow screens for tables that users need to read rather than just scan.
1<style>
2 .table-container {
3 overflow-x: auto;
4 max-width: 100%;
5 margin: 1em 0;
6 }
7
8 .responsive-table {
9 width: 100%;
10 border-collapse: collapse;
11 }
12
13 .responsive-table th,
14 .responsive-table td {
15 padding: 12px 15px;
16 border: 1px solid #ddd;
17 }
18
19 .responsive-table th {
20 background-color: #f2f2f2;
21 font-weight: bold;
22 }
23
24 @media screen and (max-width: 600px) {
25 .responsive-table thead {
26 display: none;
27 }
28
29 .responsive-table tbody,
30 .responsive-table tr,
31 .responsive-table td {
32 display: block;
33 width: 100%;
34 }
35
36 .responsive-table tr {
37 margin-bottom: 1em;
38 border: 1px solid #ddd;
39 }
40
41 .responsive-table td {
42 text-align: right;
43 padding-left: 50%;
44 position: relative;
45 border: none;
46 border-bottom: 1px solid #eee;
47 }
48
49 .responsive-table td::before {
50 content: attr(data-label);
51 position: absolute;
52 left: 15px;
53 width: 45%;
54 text-align: left;
55 font-weight: bold;
56 }
57 }
58</style>
59
60<div class="table-container">
61 <table class="responsive-table">
62 <thead>
63 <tr>
64 <th>Product</th>
65 <th>Description</th>
66 <th>Price</th>
67 <th>Status</th>
68 </tr>
69 </thead>
70 <tbody>
71 <tr>
72 <td data-label="Product">Widget A</td>
73 <td data-label="Description">Basic model with standard features</td>
74 <td data-label="Price">$12.99</td>
75 <td data-label="Status" class="in-stock">In Stock</td>
76 </tr>
77 <tr>
78 <td data-label="Product">Widget B</td>
79 <td data-label="Description">Premium model with advanced capabilities</td>
80 <td data-label="Price">$24.99</td>
81 <td data-label="Status" class="out-of-stock">Backordered</td>
82 </tr>
83 </tbody>
84 </table>
85</div>Responsive Techniques
overflow-x: auto on wrapper- The simplest responsive table fix - the table keeps its layout and users scroll horizontally. Works well for tables users need to compare across columns.Media query stacking- At small screen widths, switch table elements to display: block so rows become stacked cards instead of a grid.data-label attribute- Added to each td containing the column name. Used by the CSS ::before pseudo-element to show column labels when the header row is hidden on mobile.thead display: none- Hides the column header row on mobile when using the stacking approach, since the column names are shown via data-label instead.
Zebra Striping and Hover Effects
Zebra striping - alternating row background colors - is one of those table styling decisions that looks like a small cosmetic choice but has a real usability effect on wider tables where tracking which row you're reading across multiple columns is genuinely hard without some visual guide. The CSS for it is one line: tr:nth-child(even) with a background color on your tbody rows. The hover highlight is a similar idea - a slightly different background when the user is over a row helps them keep their place, especially on interactive tables. A transition property on the background-color change makes the hover feel smooth rather than jarring. One thing to watch with !important on highlighted cells: it's sometimes necessary to override the zebra striping on a specific cell, but the more you rely on !important the harder your CSS becomes to maintain, so use it sparingly.
1<style>
2 .zebra-table {
3 width: 100%;
4 border-collapse: collapse;
5 font-family: Arial, sans-serif;
6 }
7
8 .zebra-table th, .zebra-table td {
9 padding: 12px 15px;
10 text-align: left;
11 border-bottom: 1px solid #ddd;
12 }
13
14 .zebra-table th {
15 background-color: #4CAF50;
16 color: white;
17 }
18
19 .zebra-table tbody tr:nth-child(odd) {
20 background-color: #f9f9f9;
21 }
22
23 .zebra-table tbody tr:nth-child(even) {
24 background-color: #f2f2f2;
25 }
26
27 .zebra-table tbody tr:hover {
28 background-color: #e6f7ff;
29 transition: background-color 0.2s ease;
30 }
31
32 .highlight {
33 background-color: #fffacd !important;
34 font-weight: bold;
35 }
36</style>
37
38<table class="zebra-table">
39 <thead>
40 <tr>
41 <th>Product</th>
42 <th>Q1 Sales</th>
43 <th>Q2 Sales</th>
44 <th>Growth</th>
45 </tr>
46 </thead>
47 <tbody>
48 <tr>
49 <td>Widget A</td>
50 <td>$5,000</td>
51 <td>$6,200</td>
52 <td class="highlight">+24%</td>
53 </tr>
54 <tr>
55 <td>Widget B</td>
56 <td>$3,500</td>
57 <td>$4,100</td>
58 <td class="highlight">+17%</td>
59 </tr>
60 <tr>
61 <td>Widget C</td>
62 <td>$2,800</td>
63 <td>$2,500</td>
64 <td>-11%</td>
65 </tr>
66 </tbody>
67</table>Visual Enhancement Techniques
tbody tr:nth-child- Scoping nth-child to tbody means the header row isn't counted in the alternating pattern, so the first data row always gets the correct color.tr:hover- Highlights the row under the cursor. Useful on any table where users need to read across multiple columns.transition on background-color- Makes the hover color change animate smoothly. 0.2s ease is a good default - fast enough to feel responsive, slow enough to look intentional.!important for highlights- Sometimes needed to override zebra striping on a specific cell. Use it sparingly - the more !important declarations you have the harder your CSS becomes to reason about.
Best Practices for Table Styling
Keep all styling in CSS rather than on HTML attributes - if you ever need to update the look of your tables across a whole site, one CSS rule change handles everything versus hunting through every table element in your HTML. Make sure there's enough contrast between your text and background colors, especially on the zebra-striped rows where a very light background can make light text hard to read. Test on a narrow screen early rather than at the end - tables that look fine on a desktop often need specific mobile handling, and discovering that late is more work than designing for it from the start. Use consistent table classes across your project so the visual language is uniform, and avoid inline styles on table elements since they create specificity problems down the line.
Key Guidelines
CSS over HTML attributes- All table styling belongs in CSS. HTML attributes are deprecated, harder to maintain, and can't be overridden cleanly with stylesheets.Contrast and readability- Make sure text is legible against all background colors used in the table - zebra stripe colors, header backgrounds, and hover states all need adequate contrast.Consistent classes- Use the same table class names across your project so all tables share the same baseline styles and look intentionally related.Test on mobile early- Table responsiveness problems are much easier to design for from the start than to fix after a table is already built with many columns.Avoid inline styles- Inline styles on td and th elements create CSS specificity problems - a class-based approach is much easier to override and maintain.
CSS Is the Right Tool for Table Styling
The old HTML attribute approach for styling tables is something worth knowing because you'll encounter it, but it's not something worth using for anything new - CSS gives you cleaner separation of content and presentation, easier site-wide updates, responsive flexibility, and the full power of pseudo-classes and pseudo-elements that make things like zebra striping and hover effects possible. The gap between a default HTML table and a well-styled one isn't a large amount of CSS - border-collapse, some padding, a header background, and maybe row striping gets you most of the way there, and from that foundation you can add as much polish as the design needs