CSS3's border-radius property and border-collapse:collapse don't mix. How can I use border-radius to create a collapsed table with rounded corners?

Viewed 463959

Edit - Original Title: Is there an alternative way to achieve border-collapse:collapse in CSS (in order to have a collapsed, rounded corner table)?

Since it turns out that simply getting the table's borders to collapse does not solve the root problem, I have updated the title to better reflect the discussion.

I am trying to make a table with rounded corners using the CSS3 border-radius property. The table styles I'm using look something like this:

table {
    -moz-border-radius:10px;
    -webkit-border-radius:10px;
    border-radius:10px
}

Here's the problem. I also want to set the border-collapse:collapse property, and when that is set border-radius no longer works. Is there a CSS-based way I can get the same effect as border-collapse:collapse without actually using it?

Edits:

I've made a simple page to demonstrate the problem here (Firefox/Safari only).

It seems that a large part of the problem is that setting the table to have rounded corners does not affect the corners of the corner td elements. If the table was all one color, this wouldn't be a problem since I could just make the top and bottom td corners rounded for the first and last row respectively. However, I am using different background colors for the table to differentiate the headings and for striping, so the inner td elements would show their rounded corners as well.

Summary of proposed solutions:

Surrounding the table with another element with round corners doesn't work because the table's square corners "bleed through."

Specifying border width to 0 doesn't collapse the table.

Bottom td corners still square after setting cellspacing to zero.

Using JavaScript instead- works by avoiding the problem.

Possible solutions:

The tables are generated in PHP, so I could just apply a different class to each of the outer th/tds and style each corner separately. I'd rather not do this, since it's not very elegant and a bit of a pain to apply to multiple tables, so please keep suggestions coming.

Possible solution 2 is to use JavaScript (jQuery, specifically) to style the corners. This solution also works, but still not quite what I'm looking for (I know I'm picky). I have two reservations:

  1. this is a very lightweight site, and I'd like to keep JavaScript to the barest minimum
  2. part of the appeal that using border-radius has for me is graceful degradation and progressive enhancement. By using border-radius for all rounded corners, I hope to have a consistently rounded site in CSS3-capable browsers and a consistently square site in others (I'm looking at you, IE).

I know that trying to do this with CSS3 today may seem needless, but I have my reasons. I would also like to point out that this problem is a result of the w3c specification, not poor CSS3 support, so any solution will still be relevant and useful when CSS3 has more widespread support.

28 Answers

I figured it out. You just have to use some special selectors.

The problem with rounding the corners of the table was that the td elements didn't also become rounded. You can solve that by doing something like this:

    table tr:last-child td:first-child {
        border: 2px solid orange;
        border-bottom-left-radius: 10px;
    }
    
    table tr:last-child td:last-child {
        border: 2px solid green;
        border-bottom-right-radius: 10px;
    }
<table>
  <tbody>
    <tr>
      <td>
        1
      </td>
      <td>
        2
      </td>
    </tr>
    <tr>
      <td>
        3
      </td>
      <td>
        4
      </td>
    </tr>
  </tbody>
</table>

Now everything rounds properly, except that there's still the issue of border-collapse: collapse breaking everything.

A workaround is to add border-spacing: 0 and leave the default border-collapse: separate on the table.

If you want a CSS-only solution (no need to set cellspacing=0 in the HTML) that allows for 1px borders (which you can't do with the border-spacing: 0 solution), I prefer to do the following:

  • Set a border-right and border-bottom for your table cells (td and th)
  • Give the cells in the first row a border-top
  • Give the cells in the first column a border-left
  • Using the first-child and last-child selectors, round the appropriate corners for the table cells in the four corners.

See a demo here.

Given the following HTML:

SEE example below:

   
table {
  border-collapse: separate;
  border-spacing: 0;
  min-width: 350px;
}
table tr th,
table tr td {
  border-right: 1px solid #bbb;
  border-bottom: 1px solid #bbb;
  padding: 5px;
}

table tr th:first-child,
table tr td:first-child {
  border-left: 1px solid #bbb;
}
table tr th:first-child,
table tr td:first-child {
  border-left: 1px solid #bbb;
}
table tr th {
  background: #eee;
  text-align: left;
  border-top: solid 1px #bbb;
}

/* top-left border-radius */
table tr:first-child th:first-child {
  border-top-left-radius: 6px;
}

/* top-right border-radius */
table tr:first-child th:last-child {
  border-top-right-radius: 6px;
}

/* bottom-left border-radius */
table tr:last-child td:first-child {
  border-bottom-left-radius: 6px;
}

/* bottom-right border-radius */
table tr:last-child td:last-child {
  border-bottom-right-radius: 6px;
}
<div>
    <table>
        <tr>
            <th>item1</th>
            <th>item2</th>
        </tr>
        <tr>
            <td>item1</td>
            <td>item2</td>
        </tr>
        <tr>
            <td>item1</td>
            <td>item2</td>
        </tr>
        <tr>
            <td>item1</td>
            <td>item2</td>
        </tr>
    </table>
</div>

You'll probably have to put another element around the table and style that with a rounded border.

The working draft specifies that border-radius does not apply to table elements when the value of border-collapse is collapse.

To the best of my knowledge, the only way you could do it would be to modify all the cells like so:

table td {
  border-right-width: 0px;
  border-bottom-width: 0px;
}

And then to get the border on the bottom and right back

table tr td:last-child {
  border-right-width: 1px;
}
table tr:last-child td {
  border-bottom-width: 1px;
}

:last-child is not valid in ie6, but if you are using border-radius I assume you don't care.

EDIT:

After looking at your example page, it appears that you may be able to work around this with cell spacing and padding.

The thick gray borders you are seeing are actually the background of the table (you can see this clearly if you change the border color to red). If you set the cellspacing to zero (or equivalently: td, th { margin:0; }) the grey "borders" will disappear.

EDIT 2:

I can't find a way to do this with only one table. If you change your header row to a nested table, you might possibly be able to get the effect you want, but it'll be more work, and not dynamic.

Actually you can add your table inside a div as its wrapper. and then assign these CSS codes to wrapper:

.table-wrapper {
  border: 1px solid #f00;
  border-radius: 5px;
  overflow: hidden;
}

table {
  border-collapse: collapse;
}

Here is a way:

div {
  border: 2px solid red;
  overflow: hidden;
  border-radius: 14px;
  transform: rotate(0deg);
}
table {
  border-spacing: 0;
  background-color: blue;
  height: 100%;
  width: 100%;
}
<div>
  <table>
    <tr>
      <td><br></td> 
    </tr>
  </table>
</div>

Or

    div {
      ...
      overflow: hidden;
      border-radius: 14px;
      position: relative;
      z-index: 1;
    }
        
        

Found this answer after running into the same problem, but found it's pretty simple: just give the table overflow:hidden

No need for a wrapping element. Granted, I don't know if this would have worked 7 years ago when the question was initially asked, but it works now.

Table with rounded corners and with bordered cells. Using @Ramon Tayag solution.

The key is to use border-spacing: 0 as he points out.

Solution using SCSS.

$line: 1px solid #979797;
$radius: 5px;

table {
  border: $line;
  border-radius: $radius;
  border-spacing: 0;
  th,
  tr:not(:last-child) td {
    border-bottom: $line;
  }
  th:not(:last-child),
  td:not(:last-child) {
    border-right: $line;
  }
}

easiest way...

table {
 border-collapse: inherit;
 border: 1px solid black;
 border-radius: 5px;
}

I see a lot of weird hacks and workarounds so I would like to suggest my solution for creating a table with border-radius and the same visual effect as border: collapse; by simply targeting nested rows and cells to turn borders off.

You can get more in-depth to suit your needs using other pseudo selectors like first-child, etc, but this is the minimal solution:

table {
  width: 100%;
  border-spacing: 0;
  border-radius: 4px;
  border: 1px solid #ccc;
}

th, td {
    border-right: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
}

th:last-child, td:last-child  {
    border-right: none;
}

tr:last-child td {
    border-bottom: none;
}
<table>
  <thead>
    <tr>
      <th>Company</th>
      <th>Contact</th>
      <th>Country</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alfreds Futterkiste</td>
      <td>Maria Anders</td>
      <td>Germany</td>
    </tr>
    <tr>
      <td>Centro comercial Moctezuma</td>
      <td>Francisco Chang</td>
      <td>Mexico</td>
    </tr>
  </tbody>
</table>

The best solution so far comes from your own solution and it goes like this:

table, tr, td, th{
  border: 1px solid; 
  text-align: center;
}

table{
 border-spacing: 0;
  width: 100%;
  display: table;
}

table tr:last-child td:first-child, tr:last-child, table {
    border-bottom-left-radius: 25px;
}

table tr:last-child td:last-child, tr:last-child, table {
    border-bottom-right-radius: 25px;
}


table tr:first-child th:first-child, tr:first-child, table {
    border-top-left-radius: 25px;
}

table tr:first-child th:last-child, tr:first-child, table {
    border-top-right-radius: 25px;
}
<table>
  <tr>
    <th>Num</th><th>Lett</th><th>Lat</th>
  </tr>
  <tr>
    <td>1</td><td>A</td><td>I</td>
  </tr>
  <tr>
    <td>2</td><td>B</td><td>II</td>
  </tr>
  <tr>
    <td>3</td><td>C</td><td>III</td>
  </tr>
</table>

table {
  width: 200px;
  text-align: center;
  border-radius: 12px;
  overflow: hidden;
}

table td {
  border-width: 1px 0 0 1px;
}

table tr:first-child td {
  border-top: none;
}

table tr td:first-child {
  border-left: none;
}

div {
  background-color: lime;
}
<table cellspacing="0" cellpadding="0" border="1">
  <tr>
    <td><div>1</div></td>
    <td>1</td>
    <td>1</td>
    <td>1</td>
  </tr>
  <tr>
    <td>1</td>
    <td>1</td>
    <td>1</td>
    <td>1</td>
  </tr>
  <tr>
    <td>1</td>
    <td>1</td>
    <td>1</td>
    <td>1</td>
  </tr>
</table>

Some of the other answers are good, but none of them consider you using thead, tbody and tfoot. Or cases, when you can either combination of those. And when you apply them, you can get some unnecessary rounding or borders. Thus I tried adjusting css-only answer from @NullUserException and this is what I got:

table {
    border-radius: 5px;
    border-width: 2px;
    border-style: solid;
    border-color: darkgreen;
    border-spacing: 0;
    border-collapse: separate;
    width: 100%;
}
table tr td,
table tr th {
    border-right-width: 2px;
    border-right-style: solid;
    border-right-color: darkgreen;
    border-bottom-width: 2px;
    border-bottom-style: solid;
    border-bottom-color: darkgreen;
}
table tr th:last-child,
table tr td:last-child {
    border-right-width: 2px;
    border-right-style: none;
    border-right-color: darkgreen;
}
table tr:last-child td,
table tr:last-child th {
    border-bottom-width: 2px;
    border-bottom-style: none;
    border-bottom-color: darkgreen;
}
/* top-left border-radius */
table :not(tfoot) tr:first-child th:first-child,
table :not(tfoot) tr:first-child td:first-child {
    border-top-left-radius: 5px;
    border-bottom-left-radius: 0;
}

/* top-right border-radius */
table :not(tfoot) tr:first-child th:last-child,
table :not(tfoot) tr:first-child td:last-child {
    border-top-right-radius: 5px;
    border-bottom-right-radius: 0;
}

/* bottom-left border-radius */
table :not(thead) tr:last-child th:first-child,
table :not(thead) tr:last-child td:first-child {
    border-bottom-left-radius: 5px;
}

/* bottom-right border-radius */
table :not(thead) tr:last-child th:last-child,
table :not(thead) tr:last-child td:last-child{
    border-bottom-right-radius: 5px;
}

/*Handle thead and tfoot borders*/
table thead tr:first-child th,
table thead tr:first-child td {
  border-top-style: none;
}
table thead tr:last-child th,
table thead tr:last-child td {
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: darkgreen;
}
table tfoot tr:last-child th,
table tfoot tr:last-child td {
  border-bottom-style: none;
}
table tfoot tr:first-child th,
table tfoot tr:first-child td {
  border-top-style: solid;
  border-top-width: 2px;
  border-top-color: darkgreen;
}
table tr:first-child th,
table tr:first-child td {
  border-top-style: none;
}

darkgreen is used to clearly show that borders are correct everywhere across the whole table. Essentially, wherever you see darkgreen - is where you style the table's borders.
This codepen shows regular table and the one with thead, tbody and tfoot. CSS is identical to the one above with only the addition of style reset for th. At the moment of writing, you can see, that they both render identically.

Use "overflow: hidden" with "border-radius" This works with "collapse" table as well

Example:

border-radius: 1em; overflow: hidden;

Related