Building a grid generator using Less

Here's how to build a grid generator using Less CSS that can be easily configured to give any number of required columns and spacing.

I've had a bit of spare time recently so have used it to expand upon my 'base' set of CSS. These are the files I use when I'm starting a new project to give me a head start on some stuff, such as reset, grid, simple form styles, button elements and so on. It's a bit like Inuit only since I've written myself it I'm thoroughly familiar with it if I want to change something.

Some time ago I rewrote it all using Less CSS. At the time I was a little apprehensive of Less but decided that the benefits outweigh the detriments, particularly if used carefully (also since then I use Less for all my CSS without hesitation). I'd gone through the Less documentation a few times but generally had decided to only use a fraction of the functionality on offer. I get enough problems working on real code already without turning CSS into a programming language as well.

I'd decided to improve my grid and in this case I realised that letting myself go a bit more crazy with Less was acceptable, simply because:

  • the grid is isolated in a single LESS stylesheet and could be clearly documented
  • what the hey

It felt like an inevitable move anyway. I'd already started using variables to define the grid width, then a mixin to generate column indents, so it came naturally to then look into dynamically generating the entire grid on the basis of a few starting variables. I started with a grid maximum width and a variable for the gutter between columns, which were then used to calculate percentage based column widths. From there the natural progression seemed to be a variable to determine the number of columns, and behold, a grid generator is born.

Incidentally I'm not doing anything here that you can't get through LESS frameworks like Gumby and Bootstrap. If you want to generate a grid you can use theirs or a dozen other CSS grid generators that are out there. For me this is a learning experience - it's interesting to know how these things work and try it for myself.

Let's start with some variables.

@gridWidth: 940px;
@gridSpacing: 20;
@numCols: 12;

@gridWidth will be used as the grid wrapper's maximum width and also as a basis for all percentage calculations. @gridSpacing will be the column gutter, i.e. the space between each column. @numCols is the number of columns in our grid. Note that @gridWidth must be a fixed number as opposed to '100%' although this doesn't have to constrain the grid width. Since everything will be based on percentages we can put almost any number in at the start then manually set the grid wrapper's maximum width later, or simply turn it off.

Now for some simple calculations.

@percentColGutter: percentage(@gridSpacing / @gridWidth);
@percentColWidth: percentage(((@gridWidth - (@gridSpacing * (@numCols - 1))) / @numCols) / @gridWidth);

It took me a moment to understand Less's percentage function as it conflicted in my head with how percentages should be calculated. Basically it takes a number between 0 and 1 and turns it into a percentage - e.g. 0.55 would be output as 55%. I got some initially wildly large margins trying to do a traditional percentage calculation and hoping that the percentage function simply added a % sign on the end.

The next step was to dynamically generate all the columns. For this I needed to know how to do a Less loop, helpfully provided by stackoverflow...

@iterations: @numCols;
.loopingClass(@index) when (@index > 0){
    .col@{index}{
        width:@percentColWidth * @index + (@percentColGutter * (@index - 1));
    }
    .loopingClass(@index - 1);
}
.loopingClass(0){}
.loopingClass(@iterations);

My grid is designed for columns to have a left margin only and sets first-child grid elements with a zero left margin, i.e.

[class^="col"] {
    float:left;
    margin-left:@percentColGutter;
    &:first-child {
        margin-left:0;
    }
}

Next, I want to allow grid columns to have offsets. Applying a similar technique for columns, we get the following.

/* build all the indents */
@iterations2: @numCols - 1;
.loopingClass2(@index2) when (@index2 > 0){
    .indent@{index2}{
        .indent(@index2,1);
        &:first-child {
            .indent(@index2);
        }
    }
    .loopingClass2(@index2 - 1);
}
.loopingClass2(0){}
.loopingClass2(@iterations);

You'll notice I've created new variables for the loopingClass and index variables. This is because you need to. The first time I tried this I reused the variables from the first loop, reasoning that you can do this in other languages. Turns out that while this might compile (eventually) it seems to get stuck in a semi-infinite loop, resulting in hundreds of declarations of exactly the same class and an 800KB CSS file where it should be less than 20. Ever seen a web browser try to deal with an 800KB stylesheet? Not pretty.

The first-child class is needed because of our first-child zero left margin approach.

At this point IE7 support is already looking a little flaky - normal grid works but grid within grid is broken. Having spent what feels like a lifetime struggling to support obsolete browsers (not to mention IE6 - no, really, let's not mention it) I've finally decided I'm okay with this. It's time to let go.

.indent() is a mixin for generating the correct offset.

.indent(@multiply, @add:0){
    margin-left:@percentColWidth * @multiply + @percentColGutter * @multiply + @percentColGutter * @add;
}

That's pretty much it. Our grid markup looks like this.

<div class="gridwrapper">
	<div class="row">
		<div class="col6">Column 6</div>
		<div class="col4">Column 4</div>
	</div>
</div>

Our final touch is to add some responsiveness to our grid. Since it's based on percentages and a maximum width rather than a fixed width it's pretty responsive already, but it needs to do something for very small screens - collapse into a single column below a certain screen size.

@media screen and (min-width:0px) and (max-width:750px){
    .gridwrapper {
        .row {
            [class^="col"] {
                float:none;
                width:auto;
                margin-left:0;
                [class*="indent"] {
                    margin-left:0;
                    &:first-child {
                        margin-left:0;
                    }
                }
            }
        }
    }
}

So there's my grid. So far it's fitted every use case I've thrown at it - all you have to do is alter the variables at the top to get the right grid width, grid spacing and number of columns. I've not had to build a grid since, although I have extended it a little in the meantime.

You can see it in action on this very site, or you can take a look here at the current version of this code. A fully responsive grid that allows quick adjustment of column numbers and gutter sizes, in just under 100 lines of Less.

Important

Somewhere between version 1 of less.js and the current version the syntax for doing a loop changed slightly, resulting in sudden Less compilation errors, which don't always clearly point to the problem. I've used the current syntax above, but if you're running an old version of less.js you will need to replace this syntax...

.indent@{index2}{

with this.

(~".indent@{index2}"){

Or you could just update your Less compiler, which is probably easier. Incidentally I've come across this problem trying to compile Bootstrap as well.

Related

This article is tagged with