Simple ‘Mixin’ Alternative To Standard CSS Grids
Tags: css, grids
Published: 2022-03-12 13:22:05


Using CSS grid to generate layouts in a design provides developers with value, and makes things a lot easier for developers, but there are still some problems or downsides of using the CSS grid systems. The main reason designers and web developers use [CSS grid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout) systems is to make the generation of layouts easier and faster. But the question is “can the generation of layouts be made easier and faster, and still overcome these problems?”. Fortunately for us, the answer is “Yes!’, all thanks to CSS preprocessors such as [LESS](https://lesscss.org/) and [SASS/SCSS](https://sass-lang.com/) that can work with CSS variables, and perform calculations and output CSS via ‘mixins’.

**Outline**


- How CSS Grid System Works
- Some Downsides Of Using CSS Grids
    - High amounts of unused code
    - Restrictions on layout
    - set number of  overall columns
    - Non semantic markup
    - Restrictions on nesting
- An Alternative Solution
- Tackling The Classic Layout
    - Easy px to em/rem conversion
    - .Row() mixin
    - .Cols() mixin
- Adding Padding And Margins
    - Adding padding
    - Adding wrapping padding
    - Adding column gutters
- Nesting And Increasing Layout Complexity
- Conclusion


## **How CSS Grid Systems Work**

A grid system is usually made up of a specific number of columns, normally 12 or 16 columns, and the ability to set an element, like the `div` or `class` element, to a width of X columns. Using CSS grid systems to create layouts on a website can be really easy, and of course, there are many values you can get from using CSS grid systems, but there are also downsides to using the CSS grid system. You should not think that using the CSS grid is bad because it isn’t. But as a tool you will be using consistently in your codes, you should be able to know the pros and cons, to determine if it is right for a specific project.


## **Some Downsides Of Using CSS Grids**

Some of the downsides of using CSS Grids include:


-  **High amounts of unused code**

      Grid classes could be anywhere from 200 to 700 lines of code, but even in a  simple design,             most of the codes may never be used.
      

- **Restrictions on layout**

      Grids are already calculated, they tend to have set widths that are difficult to change, e.g.                 column widths, gutter widths. The design becomes restricted to working only those widths.


- **Set number of overall columns**

      Already calculated grid classes normally uses 12 or 16 columns. Let’s say you wanted a                     different number of columns, well, that won’t be possible.  
          

- **Non Semantic markup**

      The use of grid classes tends to bring about the placement of  numerous non-semantic class           names all through  a document, such as a “row” or  “column”.
      

- **Restrictions on nesting**

      Most times, grid systems can only have columns that are nested inside inside one another.               This restricts the complexity and flexibility of layout generation.


## **An Alternative Solution**

‘Mixins’ are available in both [LESS](https://lesscss.org/) and [SASS/SCSS](https://sass-lang.com/). Mixins are reusable packages of rules that outputs different CSS depending on the information that  is passed through them for processing. If you are not yet familiar with the understanding of what preprocessors are, and how they work with mixins and variables, you can read about it from http://lesscss.org/.  Before we get started, you should know that there are already some existing libraries of LESS and SASS  mixins that are already solving the problems if CSS grids, but we are going to be taking a different approach. 

**Note:** 
I am going to be writing these mixins in LESS, because it is harder to make LESS perform sophisticated operations than SASS, so it is easier for SASS users to adapt LESS mixins.


## **Tackling The Classic Layout**

Let’s create a layout via LESS mixins, keeping the amount of CSS required to a minimum, with no pixel values, and maintaining a semantic markup. Let’s look at the HTML below:



    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Classic Layout</title>
        <script type='text/javascript' src='js/modernizr.js'></script>
        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/classiclayout.css">
    </head>
    <body>
    <header>
         
    </header>
    <main>
        <article>
     
        </article>
        <aside>
     
        </aside>
    </main>
    <footer>
     
    </footer>
    </body>
    </html>

If you notice, there are no classes used here, only semantic HTML5 tags. I am going to be adding the following LESS code, using mixins and variables:


    header, main, footer {
        .Row;
        background-color: #ccc;
        min-height: 200 * @toRems;
    }
     
    article {
        .Cols( 3 );
        min-height: 500 * @toRems;
        background-color: #ddd;
    }
     
    aside {
        .Cols( 1 );
        min-height: 500 * @toRems;
        background-color: #eee;
    }

it will then generate the following lines of CSS:


    header,
    main,
    footer {
      max-width: 75rem;
      width: 100%;
      margin: 0 auto;
      background-color: #ccc;
      min-height: 12.5rem;
    }
    header:before,
    main:before,
    footer:before,
    header:after,
    main:after,
    footer:after {
      content: "";
      display: table;
    }
    header:after,
    main:after,
    footer:after {
      clear: both;
    }
    article {
      width: 75%;
      float: left;
      min-height: 31.25rem;
      background-color: #ddd;
    }
    aside {
      width: 25%;
      float: left;
      min-height: 31.25rem;
      background-color: #eee;
    }

Check out the result on your browser, you will see a nice layout. Let’s check out the LESS mixins and variables that were used to create the layout you will be seeing in your browser:


    //
    // Variables for em / rem use
    //
     
    @base_px: 16; //set to the most common base px size used in browsers, should generally be left at default
     
    @toRems: (1 / @base_px) + 0rem; //allows you to set values as the default px size to target, which is then converted into scalable rem values.
     
    @toEms: (1 / @base_px) + 0em; //same as above, but with em values
     
    //
    // Grid mixins
    //
     
    @default-width: 1200 * @toRems;
     
    @default-colspan: 1;
     
    @default-total_cols: 4;
     
    .Row ( @width : @default-width ) {
        max-width: @width;
        width: 100%;
        margin: 0 auto;
        // clear at the end of container
        &:before,
        &:after {
            content:"";
            display:table;
        }
        &:after {
            clear:both;
        }
    }
     
    .Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols ) {
        width: ( @colspan * (100 / @total_cols) ) + 0%;
        float: left;
    }

Now, let’s check out  each elements one by one, as you can see above:

**Easy px to em/rem conversion**


    //
    // Variables for em / rem use
    //
     
    @base_px: 16; //set to the most common base px size used in browsers, should generally be left at default
     
    @toRems: (1 / @base_px) + 0rem; //allows you to set values as the default px size to target, which is then converted into scalable rem values.
     
    @toEms: (1 / @base_px) + 0em; //same as above, but with em values

The variables that are in the top section of the code set us up for easily scalable `em` or `rem` values all through the stylesheet, instead of a `px` value. However, when we use these  values, it allows us to conceptualize our design in pixels. For example, we do not want the header, main or footer elements to be wider than  `1200px`, so instead of specifying `1200px` as the  `max-width`, we have to covert that `1200px` to rems by multiplying it with the `@toREms` variable, for example:


    1200 * @toRems;

The output value will be `75rem`, which means that if a browser or user sets the default font-size to something different from the default size of `16px`, then the entire site will scale proportionally. You can also do the same  to generate `em` values, instead of using the `@toEms` variables.

**.Row() mixin**


    @default-width: 1200 * @toRems;
     
    .Row ( @width : @default-width ) {
        max-width: @width;
        width: 100%;
        margin: 0 auto;
        // clear at the end of container
        &:before,
        &:after {
            content:"";
            display:table;
        }
        &:after {
            clear:both;
        }
    }

As you can see, the first mixin being used  is the `.Row()` mixin. Instead of using the “.container” classes, you can call this mixin wherever you want an element to  be centered with a maximum width. For our  classic layout, we call this mixin on the `header`, `main`, and `footer` elements. 

The mixin sets a `max-width` with a `width` of 100%. This will give us basic responsiveness by making the element automatically adjust to fill the available  space when the viewport gets smaller than the `max-width` value.  It also sets the `margin` to `0 auto` making the elements to be automatically centered. 

Lastly, it adds the pseudo-elements `:before` and `:after` and  also uses them to automatically clear at the end of an element. This is required so that when adding columns inside  the element, their float settings will be cleared.

The mixin accepts one parameter, a `@width` value:


    .Row (@width : @default-width ) {

The value now gets passed to the `max-width` property:


    max-width: @width;

If the mixin has a width parameter passed, e.g. `.Row(40rem)`, the value will then be applied to the  `max-width` property. 

However, if the mixin is called without passing a parameter, i.e. `.Row`, then, the default value will be used. That default value is stored in the `@default-width`  variable, which is set where you see:


    @default-width: 1200 * @toRems;

All this means that the default maximum width of any element this mixin is used on will be `1200px`, converted into `1200rem`. Using this mixin allows you to set any element to be centered at your default `max-width`, or any other `max-width` you may want to apply.  What this means is that you can change the width of the entire site by  changing the value of the `@default-width` variable. Let’s say for example: 


    @default-width: 800 * @toRems;

**.Cols() mixin**


    @default-colspan: 1;
     
    @default-total_cols: 4;
     
    .Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols ) {
        width: ( @colspan * (100 / @total_cols) ) + 0%;
        float: left;
    }

Because we are not using fixed pixels, all our column widths will have to be percentage-based, because we want our layout to remain completely flexible. To calculate these percentages, we use `.cols()` mixin. According to the values you put into the mixin, a simple formula is used to determine the percentage width value that should  be applied to the element. The element’s `float` value is set to `left`, so that columns will sit side by side (remember that we automatically clear floats that are applied to the columns via the `.Row()` mixin).

The `.cols()` mixin allows you to specify how many columns  wide you want your element to be, the same thing you could if you were using the standard CSS grid  system. This is normally done by passing a `@colspan` parameter through the mixin, or you can set the default for the mixin via the `@default-colspan` variable.

But, unlike most CSS grid systems, you have complete control over how many total that value is relative to, rather than having access to only 12 or 16 columns. You can set the total columns by passing a `@total_cols` parameter through the mixin, or you can just the mixin default via the  `@default-total_cols`  variable. Sounds cool right?

In the earlier example of a “classic” layout which I gave above, using CSS grid, the content area was set to  9 out of 12 available columns,(3/4), while the sidebar was set to 3 out of 12 columns(1/4).  Well, we do not need all 12 columns, and we do not need to break  the layout into twelve,  all we need is quarters, because we are trying to set the content area to 3/4  width, and the sidebar to 1/4 of the width. So, all we have to do now is set the value of the `@default-total_cols` to a variable of 4. For example:


    @default-total_cols: 4;

Then, we are going to use the mixins as we did in our “classic” layout above. The mixins assume that you want your columns to have a possibility of  4 total columns. So, for us  to set our article element to 3/4 width, you can do this:


    article {
        .Cols( 3 );
    }

then, if you want to set the sidebar to 1/4 width, you can do this:


    aside {
        .Cols( 1 );
    }

Let’s say you want to use a different number of total columns, we can do this by passing different values through the `.cols()` mixin. This will allow us to change the widths of the article and aside elements to any we want. Example:


    article {
        .Cols( 7, 11 ); // sets this element to span 7 of a total 11 columns
        min-height: 500 * @toRems;
        background-color: #ddd;
    }
     
    aside {
        .Cols( 4, 11 ); // sets this element to span 4 of a total 11 columns
        min-height: 500 * @toRems;
        background-color: #eee;
    }

Awesome right?


## **Adding Padding And Margins**

When you begin adding content inside your container, you definitely would want to have the ability to control the spacing around it. There are ways you can control your spacing, and the best to use depends on what you are trying to achieve with the specific design you are trying to create.

**Adding Padding**

One of the easiest ways you can control your spacing is by using the simple addition of  padding parameters to both `.Row()` and `.Cols()` mixins. We are going to adjust our mixin code as shown below:


    @default-padding: 0;
     
    .Row ( @width : @default-width; @padding: @default-padding; ) {
        max-width: @width;
        width: 100%;
        margin: 0 auto;
        padding: @padding;
        // clear at the end of container
        &:before,
        &:after {
            content:"";
            display:table;
        }
        &:after {
            clear:both;
        }
    }
     
    .Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols; @padding: @default-padding; ) {
        width: ( @colspan * (100 / @total_cols) ) + 0%;
        float: left;
        padding: @padding;
    }

We can now add padding to our header, article, aside, and footer elements. Note, we are also going to be setting the default `box-sizing` property all through the design to `border-box` so, padding does not get included in any browser calculation of the width of elements:


    * {
        box-sizing:border-box;
        -moz-box-sizing:border-box;
    }
     
    header, footer {
        .Row ( 
            @padding: 20 * @toRems;
        );
        background-color: #ccc;
        min-height: 200 * @toRems;
    }
     
    main {
        .Row;
        background-color: #ccc;
    }
     
    article {
        .Cols (
            @colspan: 3;
            @padding: 10 * @toRems 20 * @toRems;
        );
        min-height: 500 * @toRems;
        background-color: #ddd;
    }
     
    aside {
        .Cols (
            @colspan: 1;
            @padding: 10 * @toRems 20 * @toRems;
        );
        min-height: 500 * @toRems;
        background-color: #eee;
    }

This will give us  padding around each of our pieces of content, as shown below:


![](https://paper-attachments.dropbox.com/s_1441274E1BCB3917F7A0EC30DAD14A55DD03392B439014B4D098AF7BE16F2000_1645817003478_classiclayout_padded.png)


**Adding Wrapper Margins**

In some cases, the spacing that is intended to be added to a design is   on the outside of an element, instead of the inside, for example, when you want a site’s background to show through between the elements. For this  to happen, we have to make further additions to bot the  `.Row()` and `.Cols()` mixins. 

To get started, let’s allow spacing to be added above and below the elements that have our `.Row()` mixin applied to them. This can be done by simply replacing the existing `margin`  property value with a variable that can be sent as a parameter all through the mixin.


    @default-row_margin: 0 auto;
     
    .Row ( @width : @default-width; @padding: @default-padding; @margin: @default-row_margin; ) {
        max-width: @width;
        width: 100%;
        margin: @margin;
        padding: @padding;
        // clear at the end of container
        &:before,
        &:after {
            content:"";
            display:table;
        }
        &:after {
            clear:both;
        }
    }

As you can see, the default value for the row margin is still set to `0 auto`, therefore, if no parameter is passed, the mixin will still center the element automatically.

If we pass an `@margin` value through the  mixin:


    header, footer {
        .Row ( 
            @padding: 20 * @toRems;
            @margin: 10 * @toRems auto;
        );
        background-color: #ccc;
        min-height: 200 * @toRems;
    }

…we  are going to be able to add vertical spacing as see below:


![](https://paper-attachments.dropbox.com/s_1441274E1BCB3917F7A0EC30DAD14A55DD03392B439014B4D098AF7BE16F2000_1645817216353_classiclayout_vertspacerow.png)


Also, if you do not want your element to center anymore, all you have to do is pass a `@margin` value of `0 auto 0 0` to left align an element, or you can do `0 0 0 auto` to right align.

**Adding Column Gutters** 

One of the common features of CSS grid systems is its  ability to add gutters between the columns, i.e. margins that apply between each columns but not to the outside of the outermost columns. Once again we can add this functionality with some additions to the `Cols()` mixin:


    @default-gutter: 0;
     
    .Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols; @padding: @default-padding; @gutter: @default-gutter; @edge: false; ){
        @total_gutter: (@total_cols - 1) * @gutter;
        @spanned_gutters: (@colspan - 1) * @gutter;
        width: ( @colspan * ( (100 - @total_gutter) / @total_cols) ) + @spanned_gutters + 0%;
        float: left;
        padding: @padding;
        .IfEdge (@edge; @gutter);
    }
     
    .IfEdge ( @edge; @gutter; ) when (@edge = false) {
        margin-right: @gutter + 0%;
    }
     
    .IfEdge ( @edge; @gutter; ) when (@edge = true) {
        margin-right: 0;
    }

Now, the mixin does two extra things;

First, it checks for a value via the new `@gutter` parameter and goes on to factor it into the width calculation for the column. 

**Note:** the `@gutter`  ****value should be a number used as a percentage value. For example 2 for a 2% gutter.

Secondly, it goes on to check the new `@edge` variable to see is set to `true` or `false`. If it is set to false, the value of the `@guttter` parameter is added to the right margin as a percentage. IF it is set to true, the right  margin is set to `0`. This will allow you to specify where a column is at the edge of your layout and therefore, should not have an applied gutter.

In the example I will be showing you below, I will be showing you the effect of this change on the mixin. I have added two extra `article` element to our HTML. Also, I have adjusted the LESS for the `article` and `aside` elements, as shown below;


    article {
        .Cols (
            @colspan: 1;
            @padding: 10 * @toRems 20 * @toRems;
            @gutter: 1; //include a gutter of 1%
        );
        min-height: 500 * @toRems;
        background-color: #ddd;
    }
     
    aside {
        .Cols (
            @colspan: 1;
            @padding: 10 * @toRems 20 * @toRems;
            @gutter: 1; //include a gutter of 1%
            @edge: true; //this is the column on the edge so don't set a right margin
        );
        min-height: 500 * @toRems;
        background-color: #eee;
    }

the result is this;


![](https://paper-attachments.dropbox.com/s_1441274E1BCB3917F7A0EC30DAD14A55DD03392B439014B4D098AF7BE16F2000_1645820606589_classiclayout_gutters.png)


 To allow for control over vertical margins on columns, all we have to do is include some additional parameters in the mixin as shown below:
 

    @default-margin_top: 0;
     
    @default-margin_bottom: 0;
     
    .Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols; @padding: @default-padding; @gutter: @default-gutter; @edge: false; @margin_top : @default-margin_top; @margin_bottom : @default-margin_bottom; ){
        @total_gutter: (@total_cols - 1) * @gutter;
        @spanned_gutters: (@colspan - 1) * @gutter;
        width: ( @colspan * ( (100 - @total_gutter) / @total_cols) ) + @spanned_gutters + 0%;
        float: left;
        padding: @padding;
        .IfEdge (@edge; @gutter; @margin_top; @margin_bottom; );
    }
     
    .IfEdge ( @edge; @gutter; @margin_top; @margin_bottom;  ) when (@edge = false) {
        margin: @margin_top @gutter + 0% @margin_bottom 0;
    }
     
    .IfEdge ( @edge; @gutter; @margin_top; @margin_bottom;  ) when (@edge = true) {
        margin: @margin_top 0 @margin_bottom 0;
    } 

The top and bottom margin settings can now be included when we use the `.Cols()` mixin, as shown below:


    article {
        .Cols (
            @colspan: 1;
            @padding: 10 * @toRems 20 * @toRems;
            @gutter: 1;
            @margin_top: 20 * @toRems;
            @margin_bottom: 30 * @toRems;
        );
        min-height: 500 * @toRems;
        background-color: #ddd;
    }
     
    aside {
        .Cols (
            @colspan: 1;
            @padding: 10 * @toRems 20 * @toRems;
            @gutter: 1;
            @edge: true;
            @margin_top: 20 * @toRems;
            @margin_bottom: 30 * @toRems;
        );
        min-height: 500 * @toRems;
        background-color: #eee;
    }

…this would add vertical margins to the columns, as shown in the example below:


![](https://paper-attachments.dropbox.com/s_1441274E1BCB3917F7A0EC30DAD14A55DD03392B439014B4D098AF7BE16F2000_1645820948498_classiclayout_vertmargincols.png)



## **Nesting And Increasing Layout Complexity**

To add another level of complexity to our example layout, we  have to increase the number of  `article` elements to 6 and place them inside a `section` element  wrapper, then we can apply our mixins to create  the layout in the example below:


![](https://paper-attachments.dropbox.com/s_1441274E1BCB3917F7A0EC30DAD14A55DD03392B439014B4D098AF7BE16F2000_1645857572136_classiclayout_nested.png)


I have made changes  to the HTML to get the layout above, as you can see below:


    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Classic Layout</title>
        <script type='text/javascript' src='js/modernizr.js'></script>
        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/classiclayout.css">
    </head>
    <body>
    <header>
        <h1>Site Title</h1>
    </header>
    <main>
        <section>
            <h1>Latest Articles</h1>
            <article>
                <h1>Article Title</h1>
                <p>...</p>
            </article>
            <article>
                <h1>Article Title</h1>
                <p>...</p>
            </article>
            <article>
                <h1>Article Title</h1>
                <p>...</p>
            </article>
            <article>
                <h1>Article Title</h1>
                <p>...</p>
            </article>
            <article>
                <h1>Article Title</h1>
                <p>...</p>
            </article>
            <article>
                <h1>Article Title</h1>
                <p>...</p>
            </article>
        </section>
        <aside>
            <p>...</p>
        </aside>
    </main>
    <footer>
        <p>Example Footer</p>
    </footer>
    </body>
    </html>

We are also  going to change  the LESS code we are using to control the layouts as  shown below:


    header, footer {
        .Row ( @padding: 20 * @toRems; @margin: 10 * @toRems auto; );
        background-color: #ccc;
        min-height: 100 * @toRems;
    }
     
    main {
        .Row;
    }
     
    section {
        .Cols ( @colspan: 3; @padding: 10 * @toRems 20 * @toRems; @gutter: 1; );
        background-color: #ddd;
    }
     
    aside {
        .Cols ( @colspan: 1; @padding: 10 * @toRems 20 * @toRems; @gutter: 1; @edge: true; );
        min-height: 500 * @toRems;
        background-color: #eee;
    }
     
    article {
        .Cols ( @colspan: 1; @total_cols: 3; @padding: 0 20 * @toRems 20 * @toRems 20 * @toRems; @margin_bottom: 20 * @toRems;  @gutter: 2; );
        background-color: #eee;
        &:nth-of-type(3n) {
            margin-right: 0;
        }
    }

As you can  see above, the `section` element has been set to take up 3 columns of the default of 4 total columns(3/4). Next to the `section`  is the `aside` element, which is set to 1 column of 4(1/4). The `section` element acts as a wrapper  for the article. Since the article elements are now  nested, they can have all the new column width applied to them, and they will now each take up a percentage of their parent element’s interior. Therefore, each is set to 1 column out of  3, with a gutter of 2%.

All third article is identified by using the `:nth-of-type(3n)` selector and set to  have no right margin/gutter.


## **Conclusion**

We have gone through so many details in this article on how the `.Row` and `.Cols`  mixins work so that you can have an understanding, for you to be able to make further additions and modifications if you want. The result of all we have covered is actually a simple and easy-to-use layout system.

By using just two mixins, you can now create highly complex, but scalable and flexible layouts, without restrictions. You can also use any total number of columns you feel like, width of gutters, and you can nest your grids as much as needed, etc.
****