14

I'm trying to achieve a simple thing that we have in standard CSS.

Let's say that I have a grid system with 3 columns and box-sizing: border-box.

That means that I will fit 3 boxes and with a margin that will shrink the size to fit max 3 boxes.

But when I try to do that with FLEXBOX, it's a pain!!

So if I have div's with flex: 1 1 33.33%; margin: 10px; I was expecting to have 3 boxes per row... but if I use flex-wrap: wrap, that will not shrink to fit 3 boxes .

Here is a example.. the idea is that the second row would have 3 boxes in a row and fourth box would be in the last row.

Thanks

https://jsfiddle.net/mariohmol/pbkzj984/14/

.horizontal-layout {
  display: flex;
  flex-direction: row;
  width: 400px;
}

header>span {
  /* flex: 1 1 100%; */
  /* flex: 1 0 100%; */
  flex: 1 1 33.33%;
  margin: 10px;
}

header>.button {
  background-color: grey;
}

header>.app-name {
  background-color: orange;
}

header#with-border-padding {
  flex-wrap: wrap;
}

header#with-border-padding>span {
  flex: 1 1 33.33%;
  box-sizing: border-box;
  /* this is not useful at all */
}

header#with-border-padding>.button {
  border: 1px solid black;
  padding-left: 5px;
}
NO flex-wrap: wrap, so it not respects the flex 33% <br/>
<header class="horizontal-layout">
  <span class="button">A</span>
  <span class="app-name">B</span>
  <span class="button">C</span>
  <span class="button">D</span>
</header>
<br/><br/> WITH flex-wrap: wrap : I expect to have 3 boxes in first row and D box in a down<br/>
<header id="with-border-padding" class="horizontal-layout">
  <span class="button">A</span>
  <span class="app-name">B</span>
  <span class="button">C</span>
  <span class="button">D</span>
</header>
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
mariomol
  • 667
  • 1
  • 7
  • 15

2 Answers2

14

Keep in mind that box-sizing: border-box brings padding and borders into the width / height calculation, but not margins. Margins are always calculated separately.

The box-sizing property takes two values:

  • content-box
  • border-box

It does not offer padding-box or margin-box.

Consider those terms when referring to the CSS Box Model.

enter image description here

source: W3C

3.1. Changing the Box Model: the box-sizing property

content-box

The specified width and height apply to the width and height respectively of the content box of the element. The padding and border of the element are laid out and drawn outside the specified width and height.

border-box

Length and percentages values for width and height on this element determine the border box of the element. That is, any padding or border specified on the element is laid out and drawn inside this specified width and height. The content width and height are calculated by subtracting the border and padding widths of the respective sides from the specified width and height properties.


Also, an initial setting of a flex container is flex-shrink: 1. This means that flex items can shrink in order to fit within the container.

Therefore, a specified width, height or flex-basis will not hold, unless flex-shrink is disabled.

You can override the default with flex-shrink: 0.

Here's a more complete explanation: What are the differences between flex-basis and width?


Here's a simple solution:

You have four boxes. You want three on row 1 and the last on row 2.

This is what you have:

flex: 1 1 33.33%;
margin: 10px;

This breaks down to:

  • flex-grow: 1
  • flex-shrink: 1
  • flex-basis: 33.33%

We know that box-sizing: border-box factors padding and borders into the flex-basis. That's not a problem. But what about the margins?

Well, since you have flex-grow: 1 on each item, there is no need for flex-basis to be 33.33%.

Since flex-grow will consume any free space on the row, flex-basis only needs to be large enough to enforce a wrap.

Since margins also consume free space, flex-grow will not intrude into the margin space.

So try this instead:

flex: 1 1 26%;
margin: 10px;

* {
  box-sizing: border-box;
}

.horizontal-layout {
  display: flex;
  width: 400px;
}

header > span { 
  flex: 1 0 26%;                    /* ADJUSTED */
  margin: 10px;
}

header#with-border-padding {
  flex-wrap: wrap;
}

header#with-border-padding>span {
  flex: 1 0 26%;                   /* ADJUSTED */
}

header#with-border-padding>.button {
  border: 1px solid black;
  padding-left: 5px;
}

header>.button {
  background-color: grey;
}

header>.app-name {
  background-color: orange;
}
NO flex-wrap: wrap, so it not respects the flex 33% <br/>
<header class="horizontal-layout">
  <span class="button">A</span>
  <span class="app-name">B</span>
  <span class="button">C</span>
  <span class="button">D</span>
</header>
<br/><br/> WITH flex-wrap: wrap : I expect to have 3 boxes in first row and D box in a down<br/>
<header id="with-border-padding" class="horizontal-layout">
  <span class="button">A</span>
  <span class="app-name">B</span>
  <span class="button">C</span>
  <span class="button">D</span>
</header>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • 1
    thank you.. good explanation.. this is kind what i was looking for – mariomol Jul 28 '17 at 17:05
  • before reading this comment, I had two columns `col1 - flex: 1.25`, `col2 - flex: 0.75` which gave me padding issues.. Now I've changed to `col1 - flex: 1 1 62.5%`, `col2 - flex: 1 1 37.5%` no more padding issues, many thanks!! – piouson Jun 03 '21 at 16:02
  • How did you know to choose 26% for 3 elements per row? Did you have to do some maths? – Embedded_Mugs Jul 20 '21 at 05:04
  • See [my answer here](https://stackoverflow.com/a/45384426/3597276) for a more detailed explanation. @Embedded_Mugs – Michael Benjamin Jul 20 '21 at 18:08
1

As well pointed out in Michael_B's answer margins are not taken into account when calculating flex item width. But you can subtract margins from flex-basis to get desired behaviour.

For current case:

header > span {
  /* 33.33% - margin-left - margin-right */
  flex: 1 1 calc(33.33% - 20px);
  margin: 10px;
}

Also you should set box-sizing: border-box to include padding (and borders) when calculating width.

* {
  box-sizing: border-box;
}

.horizontal-layout {
  display: flex;
  width: 400px;
}

header > span { 
  flex: 1 0 calc(33.33% - 20px);
  margin: 10px;
}

header#with-border-padding {
  flex-wrap: wrap;
}

header#with-border-padding > span {
  flex: 1 0 calc(33% - 20px);
}

header#with-border-padding > .button {
  border: 1px solid black;
  padding-left: 5px;
}

header > .button {
  background-color: grey;
}

header > .app-name {
  background-color: orange;
}
NO flex-wrap: wrap, so it not respects the flex 33% <br/>
<header class="horizontal-layout">
  <span class="button">A</span>
  <span class="app-name">B</span>
  <span class="button">C</span>
  <span class="button">D</span>
</header>
<br/><br/> WITH flex-wrap: wrap : I expect to have 3 boxes in first row and D box in a down<br/>
<header id="with-border-padding" class="horizontal-layout">
  <span class="button">A</span>
  <span class="app-name">B</span>
  <span class="button">C</span>
  <span class="button">D</span>
</header>
Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
  • Thanks for the answer.. i thought about calc but think that i have a grid system where use something like `col-md-3` , so would be difficult to use that and different margins for different containers.. so i was not considering calc – mariomol Jul 13 '17 at 14:56
  • @mariomol Then you could wrap you blocks in such classes and use margins in inner blocks. – Vadim Ovchinnikov Jul 13 '17 at 15:06