5

Using either Flexbox or CSS Grid, is it possible to size my columns based on the contents of one of the columns?

Here’s What I Want:

I have 2 columns, a main and a side.

|------------------------container--------------------------|
|                                                           |
||----------------------------------------|----------------||
||               main                     |      side      ||
||----------------------------------------|----------------||
|                                                           |
|-----------------------------------------------------------|

I want the side to be automatically sized based on its content, and I want the main to be twice as wide as the side, at a minimum. It can grow bigger, but no smaller.

As the container increases, the main will grow at the same rate, while the side remains its auto width.

|--------------------------container increases----------------------|
|                                                                   |
||------------------------------------------------|----------------||
||                   main increases               |      side      ||
||------------------------------------------------|----------------||
|                                                                   |
|-------------------------------------------------------------------|

The container can shrink down to 3 times as big as the side, at which point the main will be twice as wide as the side. I want this to be the breakpoint.

|--------------------container----------------------|
|                                                   |
||--------------------------------|----------------||
||               main             |      side      ||
||--------------------------------|----------------||
|                                                   |
|---------------------------------------------------|

If the container shrinks any more, I want the columns to stack, where both columns are now 100% width:

|--------------------container--------------------|
|                                                 |
||-----------------------------------------------||
||               main                            ||
||-----------------------------------------------||
||-----------------------------------------------||
||                           side                ||
||-----------------------------------------------||
|                                                 |
|-------------------------------------------------|

Here’s What I’ve Tried:

I tried doing this with Flexbox and with Grid, but I couldn’t get the main column to determine its size based on the side column.

.container-grid {
  display: grid;
  grid-template-columns: 1fr auto; /* works, but doesn’t wrap when left column gets too small */
  /* I also tried the repeat() syntax, but that only worked for similarly-sized columns */
}
.container-flex {
  display: flex;
  flex-wrap: wrap;
}
.main { flex: 2; } .side { flex: 1; } /* same problem, doesn’t wrap */
.main ( flex: 0 1 auto; } .side { flex: 0 0 auto; } /* wraps columns too early, any time main is smaller than intrinsic width */

The only solution I’ve tried that works is knowing the fixed width of the side column (which I would have to predict without knowing its content), and then using a media query at 3 times its width. And of course, the media query only works on window width, not container width, so I would have to calculate the media query using any parent elements of the container.

.container-grid {
  display: grid;
  grid-template-columns: 1fr 15em;
}
@media screen and (max-width: 45em) {
  .container-grid {
    grid-template-columns: 1fr;
  }
}
Asons
  • 84,923
  • 12
  • 110
  • 165
chharvey
  • 8,580
  • 9
  • 56
  • 95

1 Answers1

2

Here is one solution, using Flexbox, where you use a flex-grow value being very much bigger on main than on side.

With this, the side will fill parent when wrapped, and when on wide screen, with the so much higher value on the main, the flex-growth on side will practically be none.

Combined with a min-width on the main being twice the size of the side, it will wrap before it gets smaller, and for that we can use percent as that is based on parent's width.

So in this case twice the size on main will be 2/3 of parent, 66.666%.

Fiddle demo

Stack snippet

.container-flex {
  display: flex;
  flex-wrap: wrap;
}

.main {
  flex: 10000;
  min-width: 66.666%;
  background: lightblue;
}

.side {
  flex: 1 0 auto;
  background: lightgreen;
}

span {
  display: inline-block;
  padding: 20px;
}
<div class="container-flex">

  <div class="main">
    <span>Fill remain, min. twice the "side"</span>
  </div>

  <div class="side">
    <span>Take content size</span>
  </div>

</div>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • Seems to me a perfect solution .... I don't understand your comment *the closest I can come* – vals Feb 08 '18 at 11:18
  • @vals Thanks ... will change that part, as when you say it like that it sounds strange :) – Asons Feb 08 '18 at 14:41
  • @vals I imagine the “closest I can come” part because it seems a little hacky: setting the flex-grow to an arbitrarily large number. But it looks like it might work, so I’m willing to give it a shot. – chharvey Feb 08 '18 at 18:29
  • @chharvey Yes, that is correct. And the reason is the need of setting `flex-grow` to `1` on the `side`, or else it won't fill parent when wrapped, but when unwrapped, the `flex-grow` on `main` need to be big enough so when the space left, after content being reduced, is calculated, the `main` has to take _all_, or else the `side` will grow some as well. So technically `side` will get 1/10001 of the space left and `main` gets 10000/10001, which were why I said _the closest_ :) – Asons Feb 08 '18 at 21:02
  • 1
    @LGSon - Your logic is perfectly sound; I just don't typically think that way when writing CSS. There is usually a property or value in CSS that does exactly what I want… most of the time. That’s why I called your method a bit “hacky.” But it solves the problem, so I’m marking it as the solution until a better one comes along! (FTR, I used 999999 because it’s higher and easier to type.) – chharvey Feb 08 '18 at 21:06
  • Oooh, I just now realized you can use `flex: 999999 0 66.666%;` instead of `min-width`, so that way this solution can also work for `flex-direction: column;`. – chharvey Mar 06 '18 at 19:02
  • @chharvey Technically, when setting an _initial_ `flex-basis` combined with `flex-shrink` set to zero, yes, though test if thoroughly cross browsers, as in some situations nested flex element using `flex-basis` with percent not always work, hence `width` or `min-width` can be needed. – Asons Mar 06 '18 at 19:12
  • @chharvey Also note, using percent for an element's `height` might be troublesome since elements doesn't pick percentage based `height` in the same way as it does with `width`, and this is because of elements normal default `height` value is `auto` and for `width` it is `100%`. – Asons Mar 06 '18 at 19:17