Why does flex-box work with a div, but not a table?

Cornstalks picture Cornstalks · Jan 2, 2017 · Viewed 13.3k times · Source

The following simple snippet results in a single web page that takes up the available screen space with a header at the top, a footer at the bottom, and the main content taking up as much space as possible (with a dotted border to make it easier to see):

If I modify the CSS and HTML to instead use a table in place of a div, it doesn't expand the table to consume the available space:

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-flow: column;
}

h1, small {
  flex: 0 1 auto;
}

table {
  flex: 1 1 auto;
  border: 1px dotted;
}
<!doctype html>
<html>
  <body>
    <h1>Some Header</h1>
    <table><tr><td>Some Content</td></tr></table>
    <small>Some Footer</small>
  </body>
</html>

Why does the first version (with div) work but the second version (with table) not? And is there a way to fix the second example so the table expands and consumes all available space (without introducing scroll bars)?

Some notes: my table will have a row of headers (all with equal width) and several rows (all with equal width/height). I know it's possible to recreate a table using a bunch of divs and more CSS, but going down that route feels like throwing out the baby with the bathwater (and besides, I wouldn't get to ask this question and learn if I just hacked around it). Also, JavaScript isn't available to me here (and seems like overkill).

I know enough CSS/HTML to get myself into trouble but not enough to get myself out of it...

Edit: aavrug's suggestion to use display: flex for the table makes it so the table properly expands to fill the area, but when I add multiple rows/columns to the table they are no longer equidistributed. I'd like to preserve the table's cell equidistribution.

Answer

Oriol picture Oriol · Jan 2, 2017

I think the problem is that the table box is placed inside a table wrapper box:

the table generates a principal block box called the table wrapper box that contains the table box itself and any caption boxes

enter image description here

So the table box is no longer a child of the flex container, and thus is not a flex item. The flex item is the table wrapper box, but you set the flex property to the table element, and

values of non-inheritable properties are used on the table box and not the table wrapper box

So your flex is used on a box which is not a flex item and thus is ignored.

It might have worked if that property was used on the table wrapper box, but it's not possible to select it. Even if you could, it wouldn't be clear whether it should be sized according to the tabular layout it generates instead of by the the Flexbox layout in which it participates.

The solution is simple:

  1. Place the table in a wrapper, which will be the flex item
  2. Size that flex item as desired using flex layout
  3. Take the table out of flow and give it definite lengths relative to the flex item

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}
body {
  display: flex;
  flex-flow: column;
}
h1, small {
  flex: 0 1 auto;
}
div {
  position: relative;
  flex: 1 1 0; /* Chrome needs non-auto flex-basis */
  overflow: auto;
}
table {
  position: absolute;
  height: 100%;
  width: 100%;
  left: 0;
  top: 0;
  table-layout: fixed;
  border-collapse: collapse;
}
td {
  border: 1px dotted;
  text-align: center;
}
<h1>Some Header</h1>
<div>
  <table><tr>
    <td>This</td>
    <td>is</td>
    <td>equidistributed.</td>
  </tr><tr>
    <td>This</td>
    <td>is also</td>
    <td>equidistributed.</td>
  </tr></table>
</div>
<small>Some Footer</small>

Just for fun, a hacky way to avoid adding the wrapper would be styling the table as a block and the automatically inserted tbody as the table.

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}
body {
  display: flex;
  flex-flow: column;
}
h1, small {
  flex: 0 1 auto;
}
table {
  display: block;
  position: relative;
  flex: 1 1 0;
}
tbody {
  display: table;
  position: absolute;
  height: 100%;
  width: 100%;
  left: 0;
  top: 0;
  table-layout: fixed;
  border-collapse: collapse;
  box-sizing: border-box;
}
td {
  border: 1px dotted;
  text-align: center;
}
<h1>Some Header</h1>
<table><tr>
  <td>This</td>
  <td>is</td>
  <td>equidistributed.</td>
</tr><tr>
  <td>This</td>
  <td>is also</td>
  <td>equidistributed.</td>
</tr></table>
<small>Some Footer</small>