Account for gap when calculating flex-basis
Asked Answered
L

5

24

I'm trying to use gap to specify gaps between flexed items within my grid system, but running in to a major drawback. It seems that when you're using flex-grow: 0;/flex-shrink: 0; in conjunction with gap and flex-basis values that fill the entire available width (i.e. three columns with flex: 0 0 33.3333%;), the columns overflow their parent container as the gap doesn't account for the fixed width as specified with flex: 0 0 33.3333%.

Similar to box-sizing: border-box;, is there some way to instruct the rendering engine that the gap should be subtracted when determining the width of these columns?

Demonstration:

.row {
    display: flex;
    gap: 30px;
    border: 2px solid red;
}

.col {
    flex: 0 0 33.3333%;
    background: teal;
    border: 2px solid #004D4D;
    color: white;
    font-weight: 700;
    padding: 50px;
    text-align: center;
}

:root {
    font-family: sans-serif;
}

* {
    box-sizing: border-box;
}
<h2>With gap:</h2>

<div class="row">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
</div>

<h2>Without gap:</h2>

<div class="row" style="gap:0;">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
</div>

Note: I could account for this with a formula like flex-basis: calc($width - ($gap / ($number-of-columns / 2));, but as this is for a reusable grid system, I can't practically account for every possible scenario.

Lithium answered 4/5, 2022 at 16:11 Comment(4)
Not yet AFAIK...gap isn't really that useful in flexbox IMO.Pseudocarp
Yeah, that's been my experience too. A lot of promise, a lot of problems.Lithium
Well written problem definition! I stumbled upon that problem today. To extend on my use case: I have a mini flex grid system using wrap to make it multi-line. There can be different container sizes in it and it should not need to know about the containers filling it up.Mecke
In practice, that padding: 50px; coupled with the use of gap: 0; would create the spacing with the content still wrapping in the flexbox; that's my approach in order to avoid complicated CSS calculations as the provided answersProtohistory
P
21

The formula you mentioned works... You can use CSS variables to make a reusable grid system. A buddy and I came up with this:

.flex {
  --cols: 3;
  --gap: 30px;
  display: flex;
  gap: var(--gap);
}

.flex-child {
  flex-basis: calc(100% / var(--cols) - var(--gap) / var(--cols) * (var(--cols) - 1));
}

@media (max-width: 1000px) {
  .flex{
    --cols: 2;
  }
}

@media (max-width: 750px) {
  .flex{
    --cols:1;
  }
}

So then all you need to change are the variables --cols and --gap https://codepen.io/pyledigital/pen/mdWmjQb

Pander answered 21/12, 2022 at 0:14 Comment(3)
Works! But you only need one wrapping calc, not the inner ones. Example if you only have --cols as a var: flex-basis: calc((100% / var(--cols)) - (3rem / var(--cols) * (var(--cols) - 1)));Interlope
Love this, with @NicolasA.T. 's addition (subtraction?) to reduce complexityFlexure
I use a very similar formula but with default variable values in case something isn't set: calc((100% / var(--cols, 1)) - (((var(--cols, 1) - 1) * var(--gap, 0)) / var(--cols, 1))). Combined with a SASS mixin to define gap & cols for different breakpoints in different scenarios, this has proven to be very powerful. It's unfortunate that flex doesn't take gap into account - would be far simpler to just define a width of, say, 25% and have the gap subtracted automatically.Phyl
A
4

Here is another not very elegant quick way to hack your way forward. Similar to the answer by UPinar it alters the outer flex container. Here with negative margin on the right (this might cause other layout problems!). This "solution" is using shrink 0. Also it works with a wrapping flex.

I agree that this should not be so complicated and hacky. Maybe we are missing something? I am also under the impression that this is not the really the desired box-sizing border-box behavior which I hoped to find in combination with the gap property.

flex and gap should be hack free like: Draw three containers each consuming a third of the width and have some space between em. AFAIK gap works that way with CSS grid and CSS columns.

.flex {
    display: flex;
    flex-grow: 0;
    flex-shrink: 0;
    flex-wrap: wrap;
}
.flex.gap {
   gap: var(--space-s);
   margin-right: calc(-1 * var(--space-s));
}

.col {
    flex-basis: 33.3333%;
    background: teal;
    border: 2px solid #004D4D;
    color: white;
    font-weight: 700;
    padding: 50px;
    text-align: center;
}

.flex.gap .col {
    flex-basis: calc(33.3333% - var(--space-s));
    background: teal;
    border: 2px solid #004D4D;
    color: white;
    font-weight: 700;
    padding: 50px;
    text-align: center;
}

:root {
    font-family: sans-serif;
    --space-s: 1rem;
}

* {
    box-sizing: border-box;
}
<h2>With gap</h2>

<div class="flex gap">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
    <div class="col">
        4
    </div>
    <div class="col">
        5
    </div>
    <div class="col">
        6
    </div>
</div>

<h2>Without gap</h2>

<div class="flex">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
    <div class="col">
        4
    </div>
    <div class="col">
        5
    </div>
    <div class="col">
        6
    </div>
</div>
Amathist answered 3/9, 2022 at 14:16 Comment(1)
This answer is on to something. It was the only way I could solve this problem in the context where I was having it, which was for a generic column layout solution, so I could not rely on knowing the number of flex items in advance. Furthermore, I was able to even get rid of the need to use margins entirely. The trick is to not subtract the gap space from the last flex item, only doing it on the first n-1 items. More details in github.com/Doist/reactist/pull/739 (see commit titled "Fix columns issue").Arenas
H
3

For this layout, consider Grid's flexibility over Flexbox.

See the demo:

.row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 30px;
    border: 2px solid red;
}

.col {
    background: teal;
    border: 2px solid #004D4D;
    color: white;
    font-weight: 700;
    padding: 50px;
    text-align: center;
}
<h2>With gap:</h2>

<div class="row">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
</div>
Holmgren answered 6/2 at 8:43 Comment(1)
Grid is a lot more predictable in larger layouts using gap and fr units than flex is. But you have to use fr as the width measurement and not %, because % does not factor the gap, whereas fr calculates whatever is left over.Marchpane
H
0

When you add a padding-right: calc(var(--gap-space) * 2); to parent container. Parent container width will calculte before child containers use 100% which is parent container width. You need to change parent containers width before using its width inside child container.

*,
*::before,
*::after {
  box-sizing: border-box;
}


body {
  min-height: 100vh;
  text-rendering: optimizeSpeed;
  line-height: 1.5;
  margin: 0;
  background-color: bisque;
  display: grid;
  place-content: center;
}


:root{
  --gap-space:30px;
  font-family: sans-serif;
}


.row-1 {
  display: flex;
  gap: var(--gap-space);
  border: 2px solid red;
  padding-right: calc(var(--gap-space) * 2);

}

.row-1 .col{
  background: teal;
  border: 2px solid #004D4D;
  color: white;
  font-weight: 700;
  padding: 50px;
  text-align: center;
  flex: 0 0 calc(100% / 3);
}

.row-2{
  display: flex;
  flex-direction: row;
  border: 2px solid red;
}

.row-2 .col{
  background: teal;
  border: 2px solid #004D4D;
  color: white;
  font-weight: 700;
  padding: 50px;
  text-align: center;
  flex: 0 0 calc(100% / 3);
}
<h2>With gap:</h2>

    <div class="row-1">
      <div class="col">1</div>
      <div class="col">2</div>
      <div class="col">3</div>
    </div>

    <h2>Without gap:</h2>

    <div class="row-2" style="gap: 0">
      <div class="col">1</div>
      <div class="col">2</div>
      <div class="col">3</div>
    </div>
Horowitz answered 4/5, 2022 at 16:20 Comment(1)
While flex-shrink: 1; would work in some circumstances, I often need to use flex-shrink: 0; to ensure sizing is correct. For one example, sometimes I'll have one column with flex: 1 1 0%; and another with flex: 0 0 33.3333%; which is effectively two thirds and one third, but using flex: 1 1 33.3333%; on the one third column in this case would cause it to display larger than 33.3333% of the total width.Lithium
E
0

What's wrong with using only width?

.col {
    width: 33.3333%;
  ...
}

.row {
    display: flex;
    gap: 30px;
    border: 2px solid red;
}

.col {
    width: 33.3333%;
    background: teal;
    border: 2px solid #004D4D;
    color: white;
    font-weight: 700;
    padding: 50px;
    text-align: center;
}

:root {
    font-family: sans-serif;
}

* {
    box-sizing: border-box;
}
<h2>With gap:</h2>

<div class="row">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
</div>

<h2>Without gap:</h2>

<div class="row" style="gap:0;">
    <div class="col">
        1
    </div>
    <div class="col">
        2
    </div>
    <div class="col">
        3
    </div>
</div>
Elora answered 4/5, 2022 at 17:32 Comment(2)
This works if you just want one row, but if you want the row to wrap when there are more than 3 items, this does not work - the same problem as outlined above occurs.Quartermaster
Indeed, it works only for one row. I still think it's great, because those are common cases (e.g. when you can to build a generic multi-column layout component). However, this has other drawback. It does not yield consistent widths of columns. For instance, in a 1/3 + 1/3 + 1/3 layout, the first column won't have the same width as the first column in the layout 1/3 + 2/3. See github.com/Doist/reactist/pull/739#issuecomment-1373825792 for details.Arenas

© 2022 - 2024 — McMap. All rights reserved.