6

The Goal

I'm working on a flexbox + calc() based grid system with gutters in SASS.

You define your grid via mixins like so:

.box {    
    @include column('1/3', $gutter: 20px);
}

Compiled this will be:

.box {
    width: calc(99.999999% * 1/3 - 13.33333px);
}

The Problem (please read the updates further down)

I'm trying to find a way get rid of the classic .row containers.

In my mind this should be super simple with flexbox + calc() but I have this strange bug (or an error in my caculations?) which sometimes break the layout when multiple rows should be created.

As an example:
I want a grid with a 20px gutter between the columns.

The rest is best explained in code, so here's a pen:

http://codepen.io/NilsDannemann/pen/OMBVry?editors=1100

Why is the last example (1/8 - multi row) breaking?

enter image description here


Update #1

Okay, heres whats happening: the Problem is not rooted in the flexbox space-between property, but comes from flexbox' behavior in general.

Flexbox always sees the gutters as available space. So in the last example it combines them (7 * 20px = 140px) and concludes that there is enough space to fit another item (width after calc() = 107.5px) on the first row.

This is were the whole thing falls.
space-betweenthen does it's job as intended.


Update #2

I think the only way around that is (as @Siguza pointed out) to add margins to fill the gutters. Then use an :nth-child selector to set the last margin of a row to 0.

In SASS this would look something like this:

.box {
    @include column('1/8', $gutter: 20px);
    margin-right: 20px;

    &:nth-child(8n) {
        margin-right: 0;
    }
}

Now it becomes a SASS challenge:
How do I get the :nth-child (whole number) from the passed fraction?

With some effort I have solved this. :-)
Heres the pen with the current SASS version of the grid: http://codepen.io/NilsDannemann/pen/OMaOvX?editors=1100

Wow! This works great! A multi-column grid with a completely clean DOM!
No .row classes or .eigth classes. Feel free to play around with the mixin (change the fraction or the gutter).

Heres the sad part though:
Try uneven grids like this:

.box1 {
    @include column('2/8', $gutter: 20px);;
}
.box2 {
    @include column('4/8', $gutter: 20px);;
}
.box3 {
    @include column('2/8', $gutter: 20px);;
}

Now the :nth-child is wrong and the whole thing breaks. :-(

If we can solve this (without introducing another variable to the mixin) we would have one of the cleanest any intuitive grids I have seen to date. But this last problem is a tough one.

I'm not sure if it can be done at all.
Any SASS-Genius out there to help?

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Arrowcatch
  • 1,612
  • 3
  • 19
  • 26

3 Answers3

1

The problem boils down to the definition of justify-content: space-between.

8.2. Axis Alignment: the justify-content property

The justify-content property aligns flex items along the main axis of the current line of the flex container.

space-between

Flex items are evenly distributed in the line.

source: https://www.w3.org/TR/css-flexbox-1/#justify-content-property

So, in your code, flex items will align per the rules of space-between, even when they wrap.

Extra space will be distributed differently depending on how many flex items exist on the line.

In your example illustrating the problem, you have nine flex items on the first row, and seven flex items on the second row. The second row has more extra space to distribute. Hence, the gutters are wider.

There are a total of 16 flex items in that container. Add two more, and everything aligns perfectly.

Revised Codepen

A few more considerations:

  • Flexbox is not built to serve as a grid system. The W3C is developing the CSS Grid Layout Module for this purpose.

  • Instead of width for defining the size of your flex items, consider using the flex property, which will enable you to control the width, as well as the flex-grow and flex-shrink factors.

  • Although it doesn't seem to be causing a problem in the codepen, using line comment syntax (\\ comments) is invalid in CSS and has the potential to break your code. It's safer to use the standard CSS commenting method (/* comments */). More details here: https://stackoverflow.com/a/34799134/3597276

Community
  • 1
  • 1
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • But with 18 items, they are no longer 20px apart. Also, it depends on the element width (which may depend on screen size) how many boxes are needed to have an equal amount on both rows. – Siguza Feb 05 '16 at 13:34
  • 1
    Okay, i see. Now i understand whats happening at least: basically space-between looks at the first row and sais "Oh well, theres enough space to fit one more." And thus the whole thing falls to pieces. :-/ – Arrowcatch Feb 05 '16 at 13:36
  • @Arrowcatch, are you still looking for an answer to your question? If you want to provide feedback, I'm happy to revise my answer. – Michael Benjamin Feb 08 '16 at 21:39
  • 1
    Hi @Michael_B, thanks for reaching out. Yes, actually I'm starting to get quite obsessed with this css/sass riddle :D Making progress, but still having trouble. (And I am still not sure it this actually can be done) Anyway: I'll update my original question in a minute and am VERY grateful for any kind of help or advice. – Arrowcatch Feb 09 '16 at 06:01
  • @Arrowcatch, I think I can help you with flexbox. But my knowledge of SAAS is very limited. Regrets. Good luck. – Michael Benjamin Feb 09 '16 at 11:28
0

The core issue seems to be that space-between doesn't like multiline:

#a
{
    display: flex;
    width: 190px;
    border: solid 1px #999;
    flex-wrap: wrap;
    justify-content: space-between;
}
#a > div
{
    height: 30px;
    width: 50px;
    background: #EFE;
    border: solid 1px #EEE;
}
<div id="a">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</div>

This would also happen with your 1/4 boxes, if a fifth box could fit on the first row, and it does indeed happen if you make your browser window extremely narrow.

But believe that this is seen as a feature rather than a bug:
Flexbox tries to fit as many items as possible in a row.
(But I have no source to confirm this.)

What I do have though, is a workaround.
It's a little ugly, but you can give .box a margin-right of 20px, and then take it off again every 4th .fourth box, every 8th .eight, and so on, using :nth-child():

.box {
    background: #eee;
    text-align: center;
    padding: 20px;
    margin-right: 20px;
}

// Widths: 1/4
.box.fourth {
    width: calc(100% * 1/4 - (20px - 20px * 1/4));
}
.box.fourth:nth-child(4n) {
    margin-right: 0;
}
// Widths: 1/8
.box.eigth {
    width: calc(100% * 1/8 - (20px - 20px * 1/8));
}
.box.eigth:nth-child(8n) {
    margin-right: 0;
}

[ Updated Pen ]

Siguza
  • 21,155
  • 6
  • 52
  • 89
  • Thanks for the workaround. I'll try to implement it later. Seems that this is unfortunately the only option. Damn, it looked perfect in my mind :D – Arrowcatch Feb 05 '16 at 13:38
  • Just wanted to let you know that I updated the question. Your answer pushed me in a very good direction so far! Thanks! :-) – Arrowcatch Feb 09 '16 at 07:15
-2

TL;DR flexbox is overhyped garbage and here's a grid that kind of solves this unsolvable problem.

Any SASS-Genius out there to help?

Hi. I wouldn't consider myself a "genius of Sass" or anything. I know it pretty well, but Hugo and a few others have that title.

I have been called a "grid master" by a few people. It's pretty much the only thing in life I'm good at.

I made Jeet and Lost and have literally spent hundreds of hours fighting with what you're fighting with now. It haunts me. I wake up in the morning thinking about this problem and go to bed at night thinking about this problem. I've done this for years.

Flexbox won't save you on this one. In fact, the whole "flexbox will save grid systems" meme is really sad to witness because it just doesn't have the power to be any more useful than non-flexbox CSS.

Your original problem, as Michael_B pointed out, is that you're trying to use space-between. This will never work unless you have a very specific number of elements on every line. It isn't practical to expect anyone to always have x number of elements on every line.

Then you moved onto the whole "tons of markup" (a la Bootstrap) vs. margin-right grids and it seems like you opted for the margin-right grids. I tend to lean towards these as well because I care about what my markup looks like. Bootstrap grids get confusing quickly and I've worked on countless teams where some dev went to add something and screwed up the grid by forgetting a row or trying to nest something other than a column in a row. No one notices it at first, but then out of the blue you'll see an entire chunk of the site is offset by 15px or so. Rewriting it can be a pain. I've even seen this a few times by self-proclaimed Bootstrap experts on ThemeForest.

<!-- Bootstrap markup -->
<div class="container">
  <div class="row">
    <div class="col-md-6">1</div>
    <div class="col-md-6">
      <div class="row">
        <div class="col-md-6">1a</div>
        <div class="col-md-6">
          <div class="row">
            <div class="col-md-6">1b</div>
            <div class="col-md-6">2b</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Margin-right grid markup -->
<div class="row">
  <div class="col-6">1</div>
  <div class="col-6">
    <div class="col-6">1a</div>
    <div class="col-6">
      <div class="col-6">1b</div>
      <div class="col-6">2b</div>
    </div>
  </div>
</div>

https://youtu.be/ueZ6tvqhk8U?t=18

The downside to margin-right grids is you need to get rid of the last element in a row's margin-right, which can be tricky, especially for preprocessors. This can be done by forcing the user to specify what element they want as their last one, but that introduces a whole new range of problems when you're dealing with uneven grids.

For instance, if I make a mixin that removes the margin-right from every 3rd element, but I want an assortment of sizes and shapes for my elements inside my grid, then I'm totally screwed. The only way I've found to mitigate this somewhat rare problem is to use disgusting Bootstrap markup.

This is the point where you go back to the flexbox wishing well and come up empty handed.

A week or so ago I decided to see if the Bootstrap/Foundation folks were interested in a grid that wasn't built on a markup house of cards. They declined but I put a decent amount of work into what I think is the best solution to this problem.

Here's the any-size elements with no extra markup grid Bootstrap and Foundation shot down: http://codepen.io/corysimmons/pen/MKzPWW?editors=1100

Anyway, if you like grids, stay tuned for my next big grid system set to launch early next month (I'll spam it on reddit and Hacker News). It doesn't solve this particular problem, but it will be pretty revolutionary.

If anyone is as obsessed with solving this problem as I am, I would love to chat with you about it. I'm sure I haven't thought of everything and there's some clever bastard out there just sitting on the answer.

Community
  • 1
  • 1
corysimmons
  • 7,296
  • 4
  • 57
  • 65