We have been studying Responsive Web Design and the mobile first mentality as the statistics on mobile usage are crazy! Hundreds of devices now access the web, from the traditional desktops and laptops to smartphones, tablets, game systems and TVs. This poses interesting challenges for designers as the viewport sizes of these devices range dramatically.
One solution is to have one site adapt itself to the various viewports using the power of CSS3 media queries placed atop of a flexible grid system. Conceptually this is not too complicated, but implementation can be fairly tricky, in both the design phase and writing the actual code. There are many existing open source grid systems in the wild, but to really understand how they work we wanted to get our hands dirty and create our own.
This article aims to dissect our experience and provide a toolkit that will help enable you to do the same. It does not provide a step by step tutorial due to prerequisites involved and feeling light-headed from being in Day 6 of The Master Cleanse detox!
Let's start by outlining the goals of our grid system, dubbed The Organic Grid.
We start by declaring a handful of variables that specify the grid structure. The variables are named intuitively to indicate the value they should contain.
@colCountis the total number of columns in the grid
@colWidthis the width of each column without the margin (gutter)
@colMarginis the margin of the each column, half is applied to each side of a given column
@colCombois the sum of the column width and margin and is not necessary per se, but simplifies the mixins by reducing the parentheses and operators
@gridWidthis the total width of the grid as specified in the above variables. This variable is also not a requirement, but improves readability of the mixins
Now let's assign the variables some typical values that create the common 960 pixel grid. We'll use this a target to measure our development against, particularly to verify the LESS mixin calculations.
@colCombo: @colWidth + @colMargin;
@gridWidth: @colCount * @colCombo;
This value of
@gridWidth at this point is 12 * (60 + 20) = 960. Although the units are in pixels, they are not specified (yet), as one requirement of this particular design is to function in both a fixed width pixel mode and a percentage mode.
Like most grids, The Organic Grid consists of a series of rows and columns. All columns belong to a row, that is to say, every column is contained by a row. The width summation of all the columns in a given row must equal the row width that contains them (except when using offsets). All this really entails is counting columns.
For example a row with a width of
@colCount or 12 must contain some combination of columns that add to 12, such as, an 8 width column and a 4 width column (the Golden Ratio).
This is important, all rows are a certain width (measured in columns) and the width of the columns contained need to sum up to match the row width.
With the variables in place we are ready to develop our mixins that actually use the variable values. It is worth mentioning here that mixins, like creating functions in other languages, can be defined with parameters and these parameters may contain default values. Also, be aware of the variable's scope (local vs global).
We will create two versions of each mixin, one for fixed width pixel mode and the other for fluid percentage based mode. We considered creating one version of each mixin with an argument that would accept a unit of measurement (px or %), but the code becomes more difficult to understand and troubleshoot. So even though this may violate DRY principles, it made life easier!
We will detail the basics of the fixed width methodology here that has been slightly modified to ease the learning curve. Then we will followup with a short summary how logic behind the fluid width mixins.
margin: 0 (1px * -0.5 * @colMargin);
width: 1px * ((@span * @colCombo) - @colMargin);
margin: 0 (1px * 0.5 * @colMargin)
margin-left: 1px * ((@span * @colCombo) + (0.5 * @colMargin));
.doRow() mixin sets a negative margin on each row that shifts itself (left) by half of the
@colMargin value. This technique is used to avoid targeting first and last columns and shifting them into alignment. The code is straight forward.
.doCol() mixin does two things. First it sets the width of each column as determined by the
@span passed as a parameter. So
.doCol(6) would create a column spanning 6 the equivalent of 460px as determined by 6 * (60 + 20) which totals 480, then subtracting 20.
This mixin then sets the column margin on each side of it, which in this case would yield the equivalent of 10px on the left and 10px on the right as determined by
@colMargin global variable being divided by two.
.doOffset() mixin to complete the fixed width system creates whitespace or reverses element order for SEO by giving a column more or less left-margin. The offset also takes a
@span parameter, but this time
@span expects either a positive or negative integer to indicate how much and in what direction the column should move. So in using our current example,
.doOffset(3) would push a column the equivalent of 250px to the right. This is determined by (3 * (60 + 20)) + (20 / 2). A negative value passed will pull the column to the left. Note, in either case, this affects any other sibling columns it in the document flow.
The fluid grid needs to take the pixel widths and convert them to percentages. It does this by Ethan Marcotte's simply phrased formula of target divided by context equals result.
The concept is the same as the above, except the parent container's width (in terms of number of columns) needs to be supplied to the mixins. Much of the time this will be 12, the value of
@colCount in our example. Instead of providing detail let's summarize by saying the main difference in the math is the supplied
@parentSpan is used in place of the
@colCount and each mixin name is appended the word Fluid. To put this process into words is probably more difficult than looking at the code.
margin: 0 (-50% * @colMargin) / (@parentSpan * @colCombo);
.doColFluid(@span, @parentSpan: @colCount)
width: 100% * ((@span * @colCombo) - @colMargin) / (@parentSpan * @colCombo);
margin: 0 (50% * @colMargin) / (@parentSpan * @colCombo);
margin-left: 100% * ((@span * @colCombo) + (0.5 * @colMargin)) / (@parentSpan * @colCombo);
Nesting the grid within a grid simply consists of the same process of creating any ordinary row of columns, except the base reference of
@colCount (12 columns in our example) becomes an ancestor, not a parent, of the nested rows and columns. Therefore, you need to pass its parent's column count as the
@parentSpan in the
.doRowFluid() mixin. As before explained, the nested columns width summation will need to match its parent's width.
Nesting is by far the most complex part of this process, but with a bit of practice you should be able to wrap your head around it.
The Organic Grid is an infant and yet to be battle tested, but serves as a starting point. There are browser rounding issues which will impact your design regardless of any percentage-based or sub-pixel grid system (as far as we know) because there is not a rounding standard applied by browser vendors. Opera suffers the worst of those we tested, but version 12 (alpha) greatly improves this.
Well this concludes the write-up and hopefully you find the approach useful and learned a thing or two. The code is still being polished, but we wanted to put my collection of thoughts and notes together into something useful that may help others. Let us know if you use The Organic Grid in any of your projects or have taken it apart and modified it!
If nothing else check out LESS and CodeKit (Mac application) as they have drastically altered our workflow.
We may write a followup with details as to how to use The Organic Grid specifically. An RSS feed is setup if you would like to be notified when future news is released.
These ideas are certainly not all original, we owe a lot to the great community out there sharing their work and ideas! Please see the code in the downloadable file for examples and checkout the live demo to view The Organic Grid in action!