DEV Community

loading...
Cover image for Building a Flex Layout from scratch to Fit All Projects

Building a Flex Layout from scratch to Fit All Projects

joelbonetr profile image JoelBonetR Updated on ・8 min read

With the increasing trend about grid and flex loads of developers and companies stopped using frameworks and started using this built-in CSS properties to shape their layouts. Sometimes combined and sometimes the bet is an all-in for one of this two.

Let's start with flex, which I find more interesting due to its fluidness and ease to use, as always I'm adding code and explanation for purpose.

Controlling the box-model calculations

Here's a global I usually add to control the calculations of the box model to ensure all elements are sized in this more intuitive way. You can find more information about this here.

  * { box-sizing: border-box; }

The Flex Box Layout

Flex does not fit into row-columns system by default. That's why you will see people saying grid is a 3D layout for structure, flex is a 2D layout for the content.

This is pretty accurate but you can create a fluid 3D layout with flex too using some flex values on the correct properties, let's see how to emulate a row-column layout using flex only.

Row elements

 .flexrow { 
   display: flex;
   flex-flow: row wrap;
 }

This means display the element as flex and set the flow as rows and wrap the elements if some content on a flex child does not shape into the row.

Details:

display: flex controls the display behavior.

flex-flow is a wrapper for two flex properties, in order: flex-direction and flex-wrap. In this case, direction will be "row" and the wrap is true/on. By default, the flex-wrap property is set to nowrap, which is annoying for our purposes.

Workarounds:

I'm sharing two different layouts for different purposes which can be combined, nested and used "as is".

1. Columns with the same width

This workaround will fit into rows which has N columns but all this columns having the exact same width.

 .flexrow.row-1-el > .element { 
   flex: 0 1 calc(100%/1); 
   max-width: calc(100%/1); 
 }
 .flexrow.row-2-el > .element { 
   flex: 0 1 calc(100%/2); 
   max-width: calc(100%/2);
 }
 .flexrow.row-3-el > .element { 
   flex: 0 1 calc(100%/3); 
   max-width: calc(100%/3);
 }
 /* and so... */

Note that the only difference are the class name and the number that divide the 100% of layout width.

Easy to understand, if you want a row with 2 elements (cols) at the same width, you can assume 100% as the full width and divide it by 2, getting 2 elements at 50% width.
Given that we can state:

<div class="flexrow row-2-el">
  <div class="element"> first column </div>
  <div class="element"> Second column </div>
</div>

And we are getting two columns at 50% of the available width.

Columns implementation details:

flex property is a shorthand for 3 properties which are in order: flex-grow, flex-shrink and flex-basis.

In the example we don't want this elements to grow in any way, as we are setting fixed values relative to the layout instead on letting the flex elements grow as needed.
We don't want this elements to shrink too and for the same reason we don't want them to grow.
The flex-basis specifies the initial length of a flexible item, and we are setting calculated percentiles for them to occupy the part we want or need.

As we stated that flex-basis is the initial length, we also need to state a max-width in order to keep it as we want on almost any situation. We'll use the same calc or the calc minus some margin if we want them colums for being separated.

Different values for different breakpoints:

As we are treating flex element children as fixed items (while maintaining flex behavior between them) we need to add some media queries for being able to control the value of percentile width depending on the breakpoints.

First step - Add variables at the top of your scss file.

 $screen-xs: 480px;
 $screen-sm: 768px;
 $screen-md: 992px;
 $screen-lg: 1200px;
 $screen-xl: 1600px;

Single example of breakpoint definition:


 .flexrow.row-2-el > .element { 
      flex: 0 1 calc(100%/2);
      max-width: calc(100%/2);
 }

 @media(min-width: $screen-xs) {
   .flexrow.xs-row-2-el > .element { 
     flex: 0 1 calc(100%/2);
     max-width: calc(100%/2);
   } 
 }

HTML structure example

It's time for HTML structure example for giving more context:

 <div class="flexrow row-1-el sm-row-2-el md-row-3-el>
  <div class="element> content </div>
  <div class="element> content </div>
  <div class="element> content </div>
 </div>

This is intended to be 1 element per row from 0 to sm breakpoint. From sm breakpoint to md breakpoint it will be shown as 2 elements per row. As we have 3 elements, we will see a row with 2 elements and then a row with a single element. From md breakpoint till infinite it will be shown as a row with 3 elements.

Sass / SCSS code optimization

Now we know that we need 6 declarations of each width for fitting the 5 breakpoints and the default, so we can write a mixin with a loop or a loop directly to keep the code clean and readable.

We need to decide how many columns we want on the project as much. You can discern this value looking at the designs of this project or setting the number you want and increase when you are in need.

*!important: Be aware that the output will be the $end value minus 1. So if you want the output classes to reach from 1 to 10, you'll need to set 11 as value.

 $ini: 1;
 $end: 101;

 @for $i from $ini to $end {
   .flexrow.row-#{$i}-el > .element { 
     flex: 0 1 calc(100%/#{$i}); 
     max-width: calc(100%/#{$i});
   }
   @media(min-width: $screen-xs) {
    flexrow.xs-row-#{$i}-el > div.element { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-sm) {
     .flexrow.sm-row-#{$i}-el > div.element { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-md) {
     .flexrow.md-row-#{$i}-el > div.element { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-lg) {
     .flexrow.lg-row-#{$i}-el > div.element { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-xl) {
     .flexrow.xl-row-#{$i}-el > div.element { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
 }

At this point the content on .elements is aligned at the top left inside the element so you may want to add some air on it. Also specifying the way that words must wrap helps a lot when using display flex to justify, align or nest elements inside .element.

.element { 
  padding: 15px;
  word-wrap: break-word; 
}

Result:

Click on the "Edit on Codepen" button to see it full screen, otherwise you probably will only see the smaller of the breakpoints version here on the post embed.
*
I added an outline for testing purposes, so you can see the perimeter of each content box.

2. Columns with variable width

We'll keep the same .flexrow class which will serve well for the purpose.

 .flexrow { 
   display: flex;
   flex-flow: row wrap;
 }

Now let's see how build (recursively, please) a set of columns that can fit together on different sizes.

For this purpose we can get tones of possible combinations.

The final code:

HTML example code:


<div class="flexrow">
  <div class="col-25">
    content
  </div>
  <div class="col-40">
    content
  </div>
  <div class="col-35">
    content
  </div>
</div>

CSS for the "columns":

 $ini: 1;
 $end: 101;

 @for $i from $ini to $end {
   .col-#{$i} { 
     flex: 0 1 calc(#{$i}%); 
     max-width: calc(100%/#{$i});
   }
   @media(min-width: $screen-xs) {
    .xs-col-#{$i} { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-sm) {
    .sm-col-#{$i} { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-md) {
     .md-col-#{$i} { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-lg) {
     .lg-col-#{$i} { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
   @media(min-width: $screen-xl) {
     .xl-col-#{$i} { 
       flex: 0 1 calc(100%/#{$i}); 
       max-width: calc(100%/#{$i});
     } 
   }
 }

Live example:

On the first iteration I did weird things that I rethink on a better way.
For example, I thank "what about a 75% of with plus 2 columns more?"
So I did a 75% column and two of 12.5%

What's wrong about it?

Instead getting 100 declarations for example (from 1% to 100%) you'll get near 200 (adding half points from 0.5% to 99.5%).

But then you find another issue. What about 1/3 of the width? You need to add 33.3333%, and so you'll need 66.6666%.
For combinations you also may need another intermediate values and this could be unusable after all.

How to solve it

You can simply combine both techniques.

Let's say you want a row with 1 element being the 75% of the available width, and 2 more occupying the rest at same width each.


 <div class="flexrow">
   <div class="col-75">
     75 percentile column
   </div>
   <div class="col-25 flexrow row-2-el nopadding">
     <div class="element">
       First half of 25%
     </div>
     <div class="element">
       Second half of 25%
     <div>
   </div>
 </div>

What I did here is to add both SCSS loops and semantically create a division using the first method for creating child elements, but you can also use the second methodology for dealing with this:


 <div class="flexrow">
   <div class="col-75">
     75 percentile column
   </div>
   <div class="col-25 flexrow nopadding">
     <div class="col-50">
       First half of 25%
     </div>
     <div class="col-50">
       Second half of 25%
     <div>
   </div>
 </div>

Why I coded the first methodology if with the second one can fit into all things and first one can't?

I'm testing this flex usage since january and both techniques combined work well for dealing with any layout i found till today.

You can use the first one for dealing with dynamically generated content without needing a counter for example.

Hope you enjoy the post, best regards,

Joel :)

Discussion (0)

Forem Open with the Forem app