4

I played a bit with flexboxes and tried to imitate a LaTeX-like table with top/mid/bottomrule. I like that the table is scaling nicely and shift to a list-like display on small screens.

However, I noticed that, if I use a whitespace in a cell's text content, the cell's height might double or even triple without any aparent reason.

Is there a flexbox-property that I overlooked and can help me fix this behavior? Why is this additional height even generated?

Screenshot:
enter image description here

Snippet:

div.table {
  display: flex;
  flex-flow: column wrap;
  width: 70%;
  margin: 15px auto;
  border-bottom: 2px solid black;
}
div.row {
  display: flex;
  justify-content: space-between;
  flex-flow: row wrap;
}
div.head {
  flex: 1;
  text-align: center;
  align-items: flex-start;
  font-weight: bold;
  border-width: 2px 0 1px;
  border-style: solid;
}
div.item {
  flex: 1;
  text-align: center;
  align-items: flex-start;
}
<div class="table">
  <div class="row">
    <div class="head">Col 1</div>
    <div class="head">Col 2</div>
  </div>
  <div class="row">
    <div class="item">Cell 1</div>
    <div class="item">Cell2</div>
  </div>
  <div class="row">
    <div class="item">Cell 3</div>
    <div class="item">Cell 4</div>
  </div>
</div>
Julian
  • 493
  • 4
  • 22

2 Answers2

3

The problem is that, initially, .row is sized taking only the contents of .items into account. Then .items flex, and become equally wide due to flex: 1. But the contents weren't equally wide, so the longest will wrap to a second line.

It's better shown in this example:

.row {
  display: inline-flex;
  margin: 2px;
}
.item {
  border: 1px solid;
}
.flex {
  flex: 1;
}
<div class="row">
  <div class="item">Cell 11111</div>
  <div class="item">Cell 2</div>
  (before flex)
</div>
<br />
<div class="row">
  <div class="item flex">Cell 11111</div>
  <div class="item flex">Cell 2</div>
  (after flex)
</div>

In your example you have Cell 1 (with space) and Cell2 (without), which produces the difference.

Eventually the .row also flexes and thus a new line is no longer needed, but it seems browsers don't detect that. It's hard to say, but I think this is right. That's because

  1. The hypothetical main size (height) of the rows is calculated, and

    If a cross size is needed to determine the main size (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite, in this calculation use fit-content as the flex item’s cross size.

  2. Rows flex. This determines their used height.

  3. Cross sizes (widths) are determined. The rows are stretched to fill the table horizontally. But its too late, heights have already been calculated.

Since your column layout won't wrap because .table has an auto height, I would just use the default flex-wrap: nowrap. It fixes the problem because now the flex container will be single-line, and thus the widths of the rows will be definite:

If a single-line flex container has a definite cross size, the outer cross size of any stretched flex items is the flex container’s inner cross size (clamped to the flex item’s min and max cross size) and is considered definite.

div.table {
  flex-flow: column;
}

div.table {
  display: flex;
  flex-flow: column;
  width: 70%;
  margin: 15px auto;
  border-bottom: 2px solid black;
}
div.row {
  display: flex;
  justify-content: space-between;
  flex-flow: row wrap;
}
div.head {
  flex: 1;
  text-align: center;
  align-items: flex-start;
  font-weight: bold;
  border-width: 2px 0 1px;
  border-style: solid;
}
div.item {
  flex: 1;
  text-align: center;
  align-items: flex-start;
}
<div class="table">
  <div class="row">
    <div class="head">Col 1</div>
    <div class="head">Col 2</div>
  </div>
  <div class="row">
    <div class="item">Cell 1</div>
    <div class="item">Cell2</div>
  </div>
  <div class="row">
    <div class="item">Cell 3</div>
    <div class="item">Cell 4</div>
  </div>
</div>

You can also try explicitly providing an explicit width:

div.row {
  width: 100%;
}

Or even preventing text wrapping with whitespace: nowrap.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Why doesn't this behavior occur in the first or third rows? – Michael Benjamin Jan 18 '17 at 02:16
  • @Michael_B Because in other rows, both `.item`s have the same width before flexing. – Oriol Jan 18 '17 at 02:22
  • @Michael_B You also need some space (soft wrap opportunity), or `word-break: break-all` to break inside words. – Oriol Jan 18 '17 at 02:49
  • Yes, I noticed early on that space characters matter. I just wasn't sure why. Thanks for the great explanation. – Michael Benjamin Jan 18 '17 at 02:52
  • Also, just FYI, Edge and IE11 do detect and adjust for the flexing, thus rendering the expected layout. – Michael Benjamin Jan 18 '17 at 02:52
  • @Oriol Thanks for the reply! Both `.row{width:100%}` and `.table{flex-flow: column}` fix the problem and seem to lead to identical output. However, since `.row{width:100%}` would basically fix the wrong `wrap` command, I prefer the `.table{flex-flow: column}` solution. Since I would like items to wrap in rows and words to wrap in items, `whitespace: nowrap` seems to be no solution for me. – Julian Jan 18 '17 at 17:21
0

Just change div.item from flex: 1; to only flex-grow:1;.

Here's the working snippet.

div.table {
  display: flex;
  width: 70%;
  margin: 15px auto;
  border-bottom: 2px solid black;
}
div.row {
  display: flex;
  flex-flow: row wrap;
  justify-content: space-between;
}
div.head {
  flex: 1;
  text-align: center;
  align-items: flex-start;
  font-weight: bold;
  border-width: 2px 0 1px;
  border-style: solid;
}
div.item {
  flex-grow: 1;
  text-align: center;
  align-items: flex-start;
}
<div class="table">
  <div class="row">
    <div class="head">Col 1</div>
    <div class="head">Col 2</div>
  </div>
  <div class="row">
    <div class="item">Cell 1</div>
    <div class="item">Cell2</div>
  </div>
  <div class="row">
    <div class="item">Cell 3</div>
    <div class="item">Cell 4</div>
  </div>
</div>
ab29007
  • 7,611
  • 2
  • 17
  • 43