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

4

17

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):

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

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

h1, small {
  flex: 0 1 auto;
}

div {
  flex: 1 1 auto;
  border: 1px dotted;
}
<!doctype html>
<html>
  <body>
    <h1>Some Header</h1>
    <div>Some Text</div>
    <small>Some Footer</small>
  </body>
</html>

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.

Sonnie answered 2/1, 2017 at 4:53 Comment(6)
Because table has a default style display: table; just override it with display: flex; and it will be fine.Tadd
Hmmm, doing that results in my table cells not being evenly distributed.Sonnie
I'd imagine you'd have to set the table rows to display:flex as well, since the <td> elements are a child of a child of the flex parent.Fixture
@Sonnie that's because you overwrote it to not render as a table. Just place a <div> inside and use flex on that one.Caulk
@Dodekeract That just leads back to the same problem - how do you get the table inside the flex'd div to be 100% height of the div?Kantian
@Dodekeract: Place a div where? As a wrapper around the table (e.g. <div><table><tr><td>Some Content</td></tr></table></div>)? If so, then the div takes up all the space but not the inner table.Sonnie
I
10

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>
Inhumanity answered 2/1, 2017 at 5:21 Comment(8)
"If the used flex basis is content or depends on its available space, and the flex container is being sized under a min-content or max-content constraint (e.g. when performing automatic table layout [CSS21]), size the item under that constraint. The flex base size is the item’s resulting main size."Girgenti
This looks interesting, but notice the table's height isn't expanding to 100%.Sonnie
@Cornstalks: Oh boy, another Chrome vs Firefox flexbox disparity! (Or could it be the table...?)Girgenti
@Sonnie Damn Chrome, the abspos table trick had never failed me before. Too many layouts mixed in there.Inhumanity
@Girgenti But the automatic table layout is a width algorithm, and this case is for height, so I think it's more complicated.Inhumanity
@Sonnie Chrome needs flex: 1 1 0. With flex-basis: auto it seems to think there is a circular dependency, even though it's absolutely positioned.Inhumanity
@AndreiGheorghiu Because this way height: 100% woks. Otherwise it would be a circular definition and would be treated as height: auto.Inhumanity
I think this is so far the most valuable answer and solution! Making table "display: flex" doesn't work well for table with Angular Materials mat-table directive. But your solution does work for mat-table! Thanks!Fountain
M
2

The html table element retains its display property in a flex container:

display: table

Therefore, it doesn't accept flex properties.

However, simply override that rule with display: block display: flex and the layout should work.

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}
body {
  display: flex;
  flex-flow: column;
}
h1,
small {
  flex: 0 1 auto;
}
table {
  display: flex;
  flex: 1 1 auto;
}
tbody {
  display: flex;
  width: 100%;
}
tr {
  display: flex;
  width: 100%;
}
td {
  flex: 1;
  border: 1px solid red;
}
<h1>Some Header</h1>
<table>
  <tr>
    <td>Some Content</td>
    <td>Some Content</td>
    <td>Some Content</td>
    <td>Some Content</td>
    <td>Some Content</td>
  </tr>
</table>
<small>Some Footer</small>
Machinate answered 2/1, 2017 at 5:9 Comment(6)
But then the table doesn't equidistribute it's cells (like it normally does with width: 100%; height: 100%;).Sonnie
No one in their right mind would change a table to a block box like that. It's a table! And tables have very specific layout rules that ought to be preserved unless the author specifically wants to lay them - and their contents - out in a non-tabular fashion.Girgenti
@BoltClock, my goal was to address why a table element didn't work in a flex container. I'll leave the philosophical question to you. I'm but a simple man :-)Machinate
@Michael_B: Sure, but never ever suggest changing a table to display: block! It's like the inverse of suggesting using tables for layout ;)Girgenti
@Girgenti Would you find it too hacky if I suggested table{display:block} tbody{display:table} ? XDInhumanity
@Oriol: I dunno. On second thought all that really happens with table { display: block } is that an anonymous table box gets created inside the table, so the table layout isn't destroyed, but it does mean that any properties of the table don't apply to that anonymous table box and so all you end up with is a default table box that cannot be styled. If the table only has one tbody...Girgenti
B
1

The second example (with <table> as a flex item) should be correctly stretched like the first example in Firefox 87 (bug 1674302).

Beare answered 4/3, 2021 at 23:38 Comment(0)
H
0

Well one thing, you should use vw and vh units for height and width, they are not supported throughout most browsers, but they are really good at what they can do. Also, with the table issue, you never designated it a width, so you'll only get a cell that fits the text.

Adding something like "width: 75vw;" to the table style would help (you can use %s for width)

Horrified answered 2/1, 2017 at 5:2 Comment(3)
If you are using flex you don't tend to use the width: attribute, instead you would use flex: ... which is what he did.Fixture
Ah, I see. However, I don't see why he wouldn't just do this and make the tr tags the same width.Horrified
On the contrary, vw and vh are supported in all modern browsers.Proposal

© 2022 - 2024 — McMap. All rights reserved.