Give a table visible lines by putting a border on the th and td cells, then add border-collapse: collapse to the table so neighbouring cells share one line instead of drawing two. For gaps between cells use border-collapse: separate with border-spacing. Rounded corners need the separate model, because collapsed borders ignore border-radius.
| Goal | CSS | Where it goes |
|---|---|---|
| Single shared grid lines | border-collapse: collapse | table |
| Lines around every cell | border: 1px solid #ccc | th, td |
| Gaps between cells | border-spacing: 4px | table (separate model only) |
| Striped rows | tbody tr:nth-child(even) { background: #f5f5f5 } | rows |
| Rounded outer corners | border-radius + border-collapse: separate | table and corner cells |
| Lines between rows only | border-bottom: 1px solid #ddd | th, td |
Updated
Tables have their own border rules, separate from ordinary boxes, so the CSS that works on a div often looks wrong on a table. Use the playground to switch between the collapsed and separated models, change the line width, spacing and corner radius, then copy the exact CSS it builds.
table {
border-collapse: collapse;
}
th, td {
border: 1px solid #94a3b8;
padding: 8px 12px;
}| Property | Values | Initial | Notes |
|---|---|---|---|
| border-collapse | collapse, separate | separate | Set on the table. Collapse merges shared edges. |
| border-spacing | one or two lengths | 0 | Horizontal then vertical gap. Ignored when collapsed. |
| border | width style colour | none | Usually set on th and td. |
| border-radius | lengths | 0 | Ignored in the collapsed model. |
The classic data table: one line between every cell and a tinted header.
<style>
table { border-collapse: collapse; width: 100% }
th, td { border: 1px solid #cbd5e1; padding: 6px 10px; text-align: left }
th { background: #f1f5f9 }
</style>
<table>
<tr><th>Plan</th><th>Sites</th><th>Price</th></tr>
<tr><td>Starter</td><td>1</td><td>$10</td></tr>
<tr><td>Studio</td><td>5</td><td>$30</td></tr>
</table>No vertical lines. A bottom border on each cell plus an even-row tint keeps long rows easy to follow.
<style>
table { border-collapse: collapse; width: 100% }
th, td { border-bottom: 1px solid #e2e8f0; padding: 6px 10px; text-align: left }
thead th { border-bottom: 2px solid #334155 }
tbody tr:nth-child(even) { background: #f8fafc }
td.num { text-align: right; font-variant-numeric: tabular-nums }
</style>
<table>
<thead><tr><th>Month</th><th>Visits</th></tr></thead>
<tbody>
<tr><td>July</td><td class="num">1,204</td></tr>
<tr><td>August</td><td class="num">980</td></tr>
<tr><td>September</td><td class="num">1,377</td></tr>
</tbody>
</table>The separate model with zero spacing, borders on the right and bottom of each cell, and the outer corners rounded.
<style>
table { border-collapse: separate; border-spacing: 0; width: 100%;
border: 1px solid #94a3b8; border-radius: 12px; overflow: hidden }
th, td { padding: 6px 10px; text-align: left;
border-right: 1px solid #cbd5e1; border-bottom: 1px solid #cbd5e1 }
tr > :last-child { border-right: 0 }
tr:last-child > td { border-bottom: 0 }
th { background: #e0f2fe }
</style>
<table>
<tr><th>Day</th><th>Opens</th></tr>
<tr><td>Monday</td><td>9:00</td></tr>
<tr><td>Saturday</td><td>10:00</td></tr>
</table>Both tables look alike. The second keeps the styling in CSS, which is what the HTML standard asks for.
<table border="1"><tr><td>Old</td><td>attribute</td></tr></table>
<br>
<style> .css { border-collapse: collapse } .css td { border: 1px solid #000; padding: 2px } </style>
<table class="css"><tr><td>CSS</td><td>borders</td></tr></table>
In the separated model each cell is its own box with its own border, and border-spacing sets the gap between those boxes. It is the default, which is why a bare table with cell borders looks doubled.
In the collapsed model the browser resolves each shared edge into a single line. That is what most data tables want, at the cost of losing border-spacing and rounded corners.
Horizontal rules between rows guide the eye along a line of figures better than a dense grid. Right align numeric columns, keep a clear header row with th elements, and add a caption so the table has a name for screen reader users.
Zebra stripes help on long tables with many columns. Check that the stripe colour still leaves enough contrast for the text sitting on it.
The initial value of border-collapse is separate, so every cell draws its own border and two neighbours sit side by side. Set border-collapse: collapse on the table and adjacent borders merge into one line.
Rounded corners are ignored in the collapsed border model. Switch the table to border-collapse: separate, set border-spacing: 0, then round the outer corner cells or wrap the table in a div with border-radius and overflow: hidden.
The HTML standard lists the border attribute on table as obsolete and says to use CSS instead. Browsers still draw it for old pages, but new code should set borders in a stylesheet so one rule controls every table.
One length sets the gap both across and down. Two lengths set the horizontal gap between columns first and the vertical gap between rows second, for example border-spacing: 8px 4px. It has no effect when the table is collapsed.
Target the cells in that column with a selector such as td:nth-child(2), th:nth-child(2) and give them a left or right border. Styling the cells directly works the same in both border models.
Related: HTML table generator, Markdown table generator, CSS box model and display, CSS selectors cheat sheet, HTML and CSS cheat sheet.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.