8

Using flexbox to control the layout of a table works in webkit browsers but in Firefox, <td>s only render as wide as their own content.

Demonstration: http://codepen.io/afraser/pen/wMgbzr?editors=010

* {
  box-sizing: border-box;
}
table {
  border: 1px solid #ddd;
  width: 100%;
}
tbody {
  background: #fff;
}
tr {
  display: flex;
}
td:first-child {
  flex: 1 1 80%;
  background: mistyrose;
}
td:nth-child(2) {
  flex: 0 0 10%;
  background: Aquamarine;
}
td:nth-child(3) {
  flex: 0 0 10%;
  background: pink;
}
<table>
  <tbody>
    <tr>
      <td>Ted</td>
      <td>1</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Turd Ferguson</td>
      <td>2</td>
      <td>65%</td>
    </tr>
    <tr>
      <td>Hingle McKringleberry</td>
      <td>3</td>
      <td>99%</td>
    </tr>
  </tbody>
</table>

I tried several variations on this including:

  • Using flex-grow, flex-shrink, and flex-basis individually.
  • Using pixels for the flex-basis instead of percents.
  • Using table-layout: fixed.

I see nothing documenting this here: https://github.com/philipwalton/flexbugs and have come up dry elsewhere. Does anyone know what's going on?

Oriol
  • 274,082
  • 63
  • 437
  • 513
Adam Fraser
  • 6,255
  • 10
  • 42
  • 54
  • Related: [Setting `overflow: scroll` on a table with `display: flex`](http://stackoverflow.com/q/30851336/1529630) – Oriol Jan 04 '16 at 22:59

2 Answers2

9

That's because, according to CSS tables, anonymous table objects should be generated when tabular elements are not children of a table:

enter image description here

According to the Flexbox Last Call Working Draft, it was that anonymous table what became the flex item, not the table cells:

Some values of display trigger the creation of anonymous boxes around the original box. It’s the outermost box—the direct child of the flex container box—that becomes a flex item. For example, given two contiguous child elements with display: table-cell, the anonymous table wrapper box generated around them [CSS21] becomes the flex item.

Since the table cells were not flex items, they ignored the flex property. It would apply to the anonymous table, but CSS selectors can't select anonymous elements.

However, Chrome disagreed with the spec and decided to blockify the table cells instead.

Then the CSS working group decided to standardize Chrome's behavior:

If you have a flex container and you put two table cells in it, they won't become flex items independently. They'll wrap in an anonymous table and that will be flex.

However, Chrome had implemented it so that each item is independently a flex item. [...] So it turns the table cells into blocks.

I've seen at least one presentation at a conference where they took advantage of this to create fallback behavior for a flex. [...] If you're not trying to trigger fallback, I don't know why you'd put a bunch of table cells in flex and get it wrapped in anonymous stuff. [...]

RESOLVED: Just blockify the children of flex and grid containers. Don't do anonymous box fix-up

The first Flexbox Candidate Recommendation was published with that new resolution:

Some values of display normally trigger the creation of anonymous boxes around the original box. If such a box is a flex item, it is blockified first, and so anonymous box creation will not happen. For example, two contiguous flex items with display: table-cell will become two separate display: block flex items, instead of being wrapped into a single anonymous table.

And then Firefox implemented the new behavior starting at version 47 (bug 1185140).

For older versions, you can style the cells as blocks manually:

.flex-container > td {
  display: block;
}

* {
  box-sizing: border-box;
}
table{
  border: 1px solid #ddd;
  width: 100%;
}
tbody {
  background: #fff;
}
tr {
  display: flex;
}
td {
  display: block;
}
td:first-child {
  flex: 1 1 80%;
  background: mistyrose;
}
td:nth-child(2){
  flex: 0 0 10%;
  background: Aquamarine;
}
td:nth-child(3){
  flex: 0 0 10%;
  background: pink;
}
<table>
  <tbody>
    <tr>
      <td>Ted</td>
      <td>1</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Turd Ferguson</td>
      <td>2</td>
      <td>65%</td>
     </tr>
    <tr>
      <td>Hingle McKringleberry</td>
      <td>3</td>
      <td>99%</td>
     </tr>
  </tbody>
</table>
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 1
    Great explanation. Makes sense to me now. I'm glad Chrome's version prevailed. That anonymous table wrapper stuff by FF & IE seems unnecessarily complex, less useful and less intuitive. – Michael Benjamin Jan 05 '16 at 00:24
  • Thank you so much for making sense of this and relaying the information to me. The quoted section of the editors draft is super confusing to me. Is the anonymous table wrapper being created in lieu of blockification in this case? Is that why giving it an explicit display value prevents it? – Adam Fraser Jan 05 '16 at 02:04
  • @AdamFraser Basically it's a matter of order. Table cells require a table grandparent, and if they don't have one they generate an anonymous one. Flex containers require their child boxes to be block-level, and if they aren't, they are blockified. Previously, the anonymous block-level table was generated first, and then it was blockified, remaining a block-level table. Now the table cells are blockified into blocks first, and then they no longer require a table grandparent, so they don't generate any. – Oriol Jan 05 '16 at 02:19
  • Outside of flex, I've always noticed that when building a table, even though I only use `table`, `tr` and `td`, when I review the rendered page in dev tools, it shows a `tbody`. This answer helps clarify that behavior, as well. – Michael Benjamin Jan 05 '16 at 03:35
  • @Michael_B That's handled a bit differently, though. The [CSS table model](http://www.w3.org/TR/CSS21/tables.html#table-display) does not require tables to have a table-row-group box. Instead, it's the [HTML parser](http://www.w3.org/TR/html5/syntax.html#parsing-main-intable) which inserts a tbody element automatically. IMO it's annoying, I would prefer if the DOM was as close as possible to the source code. – Oriol Jan 05 '16 at 03:43
  • This is also correct, if you use CSS classes instead of element (`td { flex: 1 0 20px; }`)selectors. – BairDev May 10 '16 at 14:10
5

I believe the issue involves the default display value of your flex items.

If you override it with display: flex the layout should work as intended across browsers.

Make the following adjustments:

td:first-child  { display: flex; }
td:nth-child(2) { display: flex; }
td:nth-child(3) { display: flex; }

Revised Codepen

My first thought was to make sure each td had the proper display value applied – something along the lines of display: flex-item. However, flex-item doesn't exist, so I used display: flex.


EDIT

The solution above stands. This edit pertains to the explanation.

On examination of the spec, it appears that flex items don't even have a default display value. Basically, once you make the parent a flex container, the children become flex items, and accept flex properties, regardless of any display rule applied. Hence, a default display rule is not necessary.

In this case, it seems that having to declare display: flex on the flex items is a quirk necessary to get Firefox and IE to work.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701