4

I am trying to implement a flexbox layout that works as follows:

  • A flexbox container with flex-direction: row contains two children.

  • The parent should only be as big as it needs to to accommodate the children (achieved via display: inline-flex).

  • The children contain text of different lengths, which does not wrap (i.e. all on one line).

  • The children should be exactly the same width, which is the width of the widest of the two texts. This is very important, and precludes the usage of flex-basis: auto, because that will cause the length of the two texts to vary rather than being exactly the same.

In order to make the children the exact same width, determined by the largest of the two, I am using flex: 1 0 0px.

The layout works exactly as expected, except that the text only seems to grow to the width as if it didn't have white-space: nowrap! The following screenshot from this codepen that I've created illustrates the problem:

Showing what happens with and without white-space: nowrap

Is there any way to implement this exact behaviour without line wrapping? I know that I can do something like add a fixed width, but I want to know if this is possible to implement dynamically (always growing to exactly the correct width of arbitrary text) using flexbox.

Thanks

.parent {
  display: inline-flex;
  margin-bottom: 60px;
}

.child {
  flex: 1 0 0px;
  padding: 20px;
}

.nowrap {
  white-space: nowrap;
  overflow: hidden;
}

.left {
  background-color: #89e7dc;
}

.right {
  background-color: #e7cc89;
}
<p>In the first example, when the text is allowed to wrap, we see that the two sides grow correctly to the same width as the larger text</p>
<div class="parent">
  <div class="child left">This is some short text</div>
  <div class="child right">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

<p>In the second example, where the text is forced onto a single line by
  <pre>white-space: nowrap; overflow: hidden;</pre> we see that the boundaries grow to the size that the wrapped text would occupy, even though it's all on one line.</p>
<div class="parent">
  <div class="child left nowrap">This is some short text</div>
  <div class="child right nowrap">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

<p>
  How can we get the same behaviour as the first example (both sides growing to exactly the size of the larger content) while keeping the text on the same line?
</p>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
mamackenzie
  • 1,156
  • 7
  • 13
  • *we see that the boundaries grow to the size that the wrapped text would occupy,* This is wrong, it's growing because of `flex:1 0 0`, if you want the text to define the width then `flex: 0 0 auto` should do it. What is the end result here ? – Rainbow May 28 '21 at 15:39
  • @ZohirSalak `flex: 0 0 auto` will result in the sides having different widths. As stated in the post, the desired behaviour is for _both sides to have the same width_, which should be the exact width of the _larger_ of the two texts. – mamackenzie May 28 '21 at 15:41
  • *which should be exact width of the larger of the two texts* This screams `flex-basis:auto` otherwise how could it be possible for the content to define the width if you ignore it ? – Rainbow May 28 '21 at 15:43
  • Please look at the codepen that I included. If you use `flex-basis: auto`, the width of each child will be determined by its own text, rather than the maximum text width of the two of them. `flex-basis: 0px` means that the growth along the flex axis will not be proportional to individual content, causing them to grow to the exact same size. – mamackenzie May 28 '21 at 15:45
  • You want the longest text to define the width of the two elements, That bit is clear. Yet you're ignore it with `flex-basis: 0` what it does is Ignore all content, split free space evenly then lay content, in this case there's no free space thanks to `inline-flex` The free space then becomes the sum of the widths of both elements as if no styles were applied – Rainbow May 28 '21 at 15:51
  • 1
    You can apply this known trick if you know before hand which is longest https://jsfiddle.net/pLw4dm9s/ – Rainbow May 28 '21 at 15:58
  • The behaviour for `flex-basis: 0` is precisely what I'm trying to achieve. What still confuses me is that it works perfectly except when `white-space: nowrap` is applied. It still insists on basing the max width on the text _as if it were wrapped_, and I have no idea why. That trick from your JSFiddle could actually be useful in a practical sense though, thanks for that. – mamackenzie May 28 '21 at 16:11

3 Answers3

6

I tried within the codepen you provided and ended up preppending the following, not sure if it's what you meant/wanted:

HTML

<div class="grid">
  <div class="child left">This is some short text</div>
  <div class="child right">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

CSS

.grid {
  /* choose one of these below depending whether you want it (the parent) take the full width */
  display: inline-grid;
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* choose one of these below depending whether you want it overflowing horizontally */
  width: fit-content;
  width: max-content;
}

The full demo:

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: max-content;
}

.parent {
  display: inline-flex;
  margin-bottom: 60px;
  width: max-content;
}

.child {
  flex: 1 0 0px;
  padding: 20px;
}

.nowrap {
  white-space: nowrap;
  overflow: hidden;
}

.left {
  background-color: #89e7dc;
}

.right {
  background-color: #e7cc89;
}
<h2>Answer</h2>

<div class="grid">
  <div class="child left">This is some short text</div>
  <div class="child right">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

<h2>Question</h2>

<p>In the first example, when the text is allowed to wrap, we see that the two sides grow correctly to the same width as the larger text</p>
<div class="parent">
  <div class="child left">This is some short text</div>
  <div class="child right">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

<p>In the second example, where the text is forced onto a single line by
  <pre>white-space: nowrap; overflow: hidden;</pre> we see that the boundaries grow to the size that the wrapped text would occupy, even though it's all on one line.</p>
<div class="parent">
  <div class="child left nowrap">This is some short text</div>
  <div class="child right nowrap">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

<p>
  How can we get the same behaviour as the first example (both sides growing to exactly the size of the larger content) while keeping the text on the same line?
</p>

It's always tricky for a child to control the parent or its siblings with CSS, and I think we got either hacky or lucky with this one... (if it at all is what you meant)

Neo
  • 157
  • 8
  • This looks pretty good with `max-content` (on Chrome). Doesn't even need `nowrap`. – Michael Benjamin May 28 '21 at 16:30
  • Wow this actually seems to work! For one-dimensional layouts I figured that grid wouldn't provide any extra capability. I'll have to look into this to understand a little better. Thanks a lot! – mamackenzie May 28 '21 at 16:41
  • 1
    You're very welcome! (Make sure you thank Alex too haha) I actually wrote an article about the less-known subtle difference between flexbox and grid, which is somewhat related to your commnet: https://dev.to/li/the-pursuit-of-difference-between-css-grid-and-flexbox-277n – Neo May 28 '21 at 16:44
  • @Neo, good article. For your follow up... https://stackoverflow.com/q/55064488/3597276 – Michael Benjamin May 28 '21 at 16:59
1

Won't work with flex-grow because this function distributes free space on the line.

This means that the longer text item will first consume the free space in the shorter item before the container can expand. This kills the equal width layout you're seeking.

Put another way, flex-grow: 1, applied to both items, will distribute free space evenly between them. However, the longer the content, the less the free space, and flex-grow can't work as you expect. It can only create equal width items with flex-basis: 0 and enough free space.

Same problem with grid: the use of free space.

I don't think this layout can be achieved with flex-grow or fr. You would need another CSS method or JS.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • I think I agree with you (although I'd be delighted to be proven wrong) that this is not possible without JS. Out of curiosity, do you know why it works perfectly if the text is allowed to wrap? Also, do you know why it gets cut off at the _exact_ width of the wrapped text when `white-space: nowrap` is applied?: – mamackenzie May 28 '21 at 16:00
  • 1
    *"Do you know why it works perfectly if the text is allowed to wrap?"* Because `flex-grow: 1` and `flex-basis: 0` can work as intended, as the longer text expands vertically, not consuming additional space on the horizontal axis. – Michael Benjamin May 28 '21 at 16:23
  • 1
    *"Do you know why it gets cut off at the exact width of the wrapped text when `white-space: nowrap` is applied?"* Because the `inline-flex` algorithm sets the same width for the container regardless of the `white-space` value. – Michael Benjamin May 28 '21 at 16:26
0

Does this work? https://codepen.io/cheapsteak/pen/qBrPVda

<div class="parent">
  <div class="left child" style="">This is some short text</div><div class="right child" style="">This is some long text that is intended to cause the previous child to grow to the width of this one</div>
</div>

<style>
.parent {
  margin-bottom: 60px;
}

.child {
  padding: 20px;
  box-sizing: border-box;
  display: inline-block;
  width: 50%;
  min-width: max-content;
}

.nowrap {
  white-space: nowrap;
  overflow: hidden;
}

.left {
  background-color: #89e7dc;
}

.right {
  background-color: #e7cc89;
}
</style>
CheapSteaks
  • 4,821
  • 3
  • 31
  • 48
  • Thanks for taking a crack at it. Unfortunately this codepen does not produce the desired behaviour, as the two sides are of different length. – mamackenzie May 28 '21 at 15:58