Advertisement
HTML/CSS

A Simple Mixin Alternative to Standard CSS Grids

by

Over the past few years CSS grid systems have become a very popular way of rapidly producing layout scaffolding in web design.

They can be a fantastic time-saving resource, with talented coders having intelligently crafted systems which cater to a wide array of possible layouts designers might need to create. However, this isn't the only way to approach grid-based web layouts, and today we're going to examine an alternative.


How CSS Grid Systems Work

A grid system typically consists of a specific number of columns, (commonly twelve or sixteen), and the ability to set an element, (usually a div with a class applied), to a width of X of those columns.

For example, a content area might be set to nine of twelve columns, while a sidebar next to it is set to three or twelve columns. Layouts often also include a fixed-width outer container, row containers within it that wrap columns, and set width gutters between columns.

Let's take a quick look at how the classic "header, content, sidebar, footer" website layout might be built using techniques common to CSS grids:

examplecssgrid

Using CSS grid systems to create layouts like this can be very quick and easy. However, while there is plenty of value on offer, there are also a number of downsides to utilizing a system like this. These downsides don't mean that CSS grid systems are "bad", only that as with any tool you should familiarize yourself with the pros and cons to properly determine if it is the right fit for your specific project.


The Downsides to CSS Grids

While all CSS grid systems are different, the most notable downsides that are commonly present among them include:

  • Potentially high amount of unused code
    A full library of grid classes tends to be anywhere from 200 to 700 lines of code (unminified), yet in a relatively simple design much of that code may never be used.
  • Restrictions on layout
    Because grids are precalculated they tend to have set widths that are difficult to change, e.g. for containers, column widths, gutter widths. The design then becomes restricted to working with those widths.
  • Fixed pixel rather than em / rem or percentage based layouts
    Grid widths and/or media queries are often based on pixel values, preventing use of more flexible and scalable em / rem or percentage values for design.
    Note: For information on the advantages of em / rem vs. px based design, please read Taking the “Erm..” Out of Ems
  • Set number of overall columns
    Precalculated grids tend to use either twelve or sixteen columns, meaning if you want a different number of overall columns you're out of luck.
  • Non semantic markup
    Use of precalculated grid classes necessitates placement of numerous non-semantic class names throughout a document, such as "row", "col" and so on.
  • Restrictions on nesting
    Often grid systems can only have columns nested inside one another one or two times, restricting the complexity and flexibility of layout generation.

When it comes down to it, the real reason designers use CSS grid systems is to make layout generation quicker and easier. So the question is:

Can layout generation be made just as quick and easy while at the same time overcoming the limitations listed above?

Thanks to the advent of CSS preprocessors such as LESS and SASS / SCSS which can work with variables, perform calculations and output CSS on an as needed basis via mixins, for a great many use cases the answer is:

Yes it absolutely can!


An Alternative, Preprocessed Solution

The key to the alternative solution we'll be covering is "mixins", available in both LESS and SASS / SCSS. In a nutshell, mixins are reusable packages of rules that can output different CSS depending on what information is passed to them for processing.

From here on we'll assume you have an essential understanding of what preprocessors are and how they work with mixins and variables, so if you're new to these concepts I recommend starting by reading the information provided at http://lesscss.org/

Before we begin, I'll also point out that there are already some existing libraries of LESS and SASS mixins that are working to address the limitations of precalculated CSS grids, such as:

However, we'll be taking an approach that differs to these libraries in various ways. Again, there's no strictly right or wrong way to approach things, it's a matter of understanding pros and cons when deciding what approach to use for your project.

Note: I'll be writing these mixins in LESS, because it can be a little harder to get LESS to perform sophisticated operations than SASS, so it will be easier for SASS users to adapt LESS mixins than the other way around.

You can use your own preferred method of compiling your preprocessor files, however I will include a basic "LESScompiler" package running on Node.js/NPM and Grunt you can use. Instructions are included in the readme.md file.


Tackling the Classic Layout

We looked above at how the ubiquitous "header, content, sidebar, footer" layout might be created using a typical set of CSS grid classes, tapping into around 200 to 700 lines of code. Now let's look at how we can create this same layout via LESS mixins, keeping the amount of CSS required to an absolute minimum, using no pixel values, and maintaining semantic markup.

First, let's look at our HTML:

<!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>

Notice there is not a single class used at this stage, only pure semantic HTML5 tags. To this HTML we will apply the following LESS code, using mixins and variables that will be explained shortly:

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;
}

...which will generate the following 35 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;
}

...and the result we will see in the browser, at a screen resolution of 1920 x 1080, is:

classiclayout

Note that because we are not working with fixed pixel width settings, this layout already has basic responsiveness present from word go. The same layout looks like this at 1024px wide:

classiclayout1024

And like this at 768px wide:

classiclayout768

We'll cover responsiveness at smaller sizes later on, but first let's check out the LESS mixins and variables used to create the layout above.

//
// 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;
}

Let's step through each element of what's going on in the 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 in the top section of the code get us setup to easily output scalable em or rem values throughout the stylesheet instead of px values. However, using these variables will also allow us to conceptualize our design in pixels to begin with, because it's an easier mental exercise to imagine what 500px of space looks like than it is 31.25rem.

For example, in our base design we don't want the header, main or footer elements to be wider than 1200 pixels at any time. But instead of specifying 1200px as the max-width, we convert that 1200 value to rems through multiplying it by the @toRems variable like so:

1200 * @toRems;

This will output a value of 75rem, meaning that if a browser or user has set the default font-size to something other than the most common default of 16px, the entire site layout will scale proportionally.

The same thing can also be done to generate em values, by instead using the @toEms variable.

.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;
	}
}

The first mixin being used is the .Row() mixin.

Instead of using ".container" classes, we call this mixin wherever we want an element to be centered with a maximum width. In the case of our classic layout we call this mixin on the header, main and footer elements.

The mixin sets a max-width along with a width of 100%. This gives us basic responsiveness through making the element automatically adjust to fill the available space whenever the viewport is smaller than the max-width value.

It also sets the margin to 0 auto so the element will automatically be centered.

Finally, it adds the pseudo elements :before and :after and uses them to automatically clear at the end of the element. This is required so that when we start to add columns inside the element their float settings will be cleared.

The mixin accepts one parameter, a @width value:

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

This value is then passed to the max-width property:

	max-width: @width;

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

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

@default-width: 1200 * @toRems;

As you'll now understand from what we covered above regarding the @toRems variable, this means the default maximum width of any element this mixin gets used on will be 1200 pixels, converted into rem values.

By using this mixin you can now set any element to be centered at your default max-width, or at any other max-width you want to apply. This means you can change the width of the entire site just by changing the value of the @default-width variable. For example:

@default-width: 800 * @toRems;

...changes the base layout to:

classiclayout800

Or you can change the width of a single element at a time, by passing a width parameter through the mixin.

For example, applying the mixin like so:

header, footer {
	.Row;
	background-color: #ccc;
	min-height: 200 * @toRems;
}

main {
	.Row( 800 * @toRems );
	background-color: #ccc;
}

...would give you:

classiclayout_differentwidths

.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 pixel widths, and we want our layout to remain completely flexible, all our column widths will be percentage based. We use the .Cols() mixin to calculate these percentages.

Based on the values you pass 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 also set to left so columns will sit side by side (you'll recall we automatically clear floats applied to the columns via the .Row() mixin).

With the .Cols() mixin you get to specify how many columns wide you want your element to be, just as you would with a regular CSS grid system. This is done by passing a @colspan parameter through the mixin, or by setting the default for the mixin via the @default-colspan variable.

However, unlike most CSS grid systems, you also have complete control over how many total columns that value is relative to, rather than being stuck with total columns of either 12 or 16. Total columns can be set by passing a @total_cols parameter through the mixin, or by setting the mixin's default via the @default-total_cols variable.

In our earlier example of how our "classic" layout would be built using a typical CSS grid, the content area was set to 9 out of 12 columns, (i.e. three quarters), while the sidebar was 3 out of 12 columns, (i.e. one quarter). However, for the simple purposes of this layout we really don't need all twelve columns.

All we are trying to do is set our content area to 3/4 width, and the sidebar to 1/4 of the width. So breaking the layout into twelfths would be overkill, when all we need is quarters.

Because we know we only need to break this layout into quarters, we can set the value of our @default-total_cols variable to 4:

@default-total_cols: 4;

Then, when we use the mixin as we have in our "classic" layout example, the mixin assumes you want your columns to be out of a possible four total columns. So to set our article element to three quarter width all we need to do is:

article {
	.Cols( 3 );
}

Then to set our aside / sidebar to one quarter width we simply use:

aside {
	.Cols( 1 );
}

However, if we decide we want to use a totally different number of total columns we can do so easily by passing different values through the .Cols() mixin.

This allows us to change the widths of the article and aside elements to anything we please extremely easily, for 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;
}

Which gives us:

classiclayout_colschanged

This in turn makes it very easy to add extra columns, for example if we add a second aside element before the article element in our HTML, and then change our LESS to the following:

article {
	.Cols( 5, 9 );
	min-height: 500 * @toRems;
	background-color: #ddd;
}

aside {
	.Cols( 2, 9 );
	min-height: 500 * @toRems;
	background-color: #eee;
}

...we will get:

classiclayout_extracol

Adding Padding and Margins

Once you start adding content inside your containers, you will of course want to be able to control the spacing around it. Right now when we add content it's going to sit flush against the edges:

classiclayout_contentflush

There are a couple of ways you can go about controlling your spacing, and which one is best to use will depend on what you are trying to achieve with the specific design you are creating.

Adding Padding

The easiest way to control spacing is with the simple addition of padding parameters to both the .Row() and .Cols() mixins.

Our mixin code will be adjusted to the following:

@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;
}

Now we can add padding to our header, article, aside and footer elements.

Note we will also set the default box-sizing property throughout the design to border-box so padding doesn't 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 now gives us padding around each of our pieces of content:

classiclayout_padded

Adding Wrapper Margins

Sometimes the spacing you want to add to your design is on the outside of an element, not the inside, for example when you want a site's background to show through in between elements. To allow this we'll make some further additions to both the .Row() and .Cols() mixins.

To begin with, let's allow for spacing to be added above and below elements that have our .Row() mixin applied to them. This is easily done simply by replacing our existing margin property's value with a variable that can be sent as a parameter 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;
	}
}

Note that the default value for the row margin is still set to 0 auto, so if no parameter is passed the mixin will still automatically center the element.

However if we do 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 can add vertical spacing like so:

classiclayout_vertspacerow

Additionally, if you decide you don't want your element to center anymore, you could also do things like passing a @margin value of 0 auto 0 0 to left align an element, or 0 0 0 auto to right align.

Adding Column Gutters

Another common feature of CSS grid systems is the ability to add gutters between columns, i.e. margins that apply in between each column, but not to the outside of the outermost columns.

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;
}

The mixin now does two extra things. Firstly, it checks for a value via the new @gutter parameter, and factors it into the width calculation for the column.

Note: The @gutter value should be a number intended for use as a percentage value e.g. 2 for a 2% gutter.

Secondly, it checks the new @edge variable to see if it is set to true or false. If @edge is set to false the value of the @gutter parameter is added to the right margin as a percentage. If @edge is set to true, the right margin is set to 0. This allows you to specify where a column is at the edge of your layout and hence should not have a gutter applied.

To show the effect of this change to the mixin more clearly I have added two extra article elements to our HTML. The LESS for the article and aside elements has been adjusted to the following:

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;
}

Which now gives us:

classiclayout_gutters

Allowing for control over vertical margins on columns is again just a matter of including some additional parameters in the mixin, adjusting it to:

@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;
}

Top and bottom margin settings can now also be included when using the .Cols() mixin, for example:

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;
}

Which would add vertical margins to the columns like so:

classiclayout_vertmargincols

Nesting and Increasing Layout Complexity

We'll now add another level of complexity to our example layout by increasing the number of article elements to six and placing them inside a section element wrapper, then applying our mixins to create the following layout:

classiclayout_nested

To achieve this layout, the HTML is now changed to:

<!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>

The LESS code we are using to control the layout has now been changed to:

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;
	}
}

To summarize what's contained in the LESS code above:

The section element is set to take up three columns of the default four total columns. Next to it is the aside element, still set to one column of four.

The section element is acting as a wrapper for the article elements. Because the article elements are now nested they can have all new column widths applied to them, and they will each take up a percentage of their parent element's interior. As such, each is set to one column out of three, with a gutter of 2%.

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


Adjusting to Smaller Displays

Earlier in the article we showed how basic responsiveness is added from the word go by using this approach. Another thing we will do differently to many others is our approach to adding breakpoints and determining behavior at these points.

Instead of trying to identify and target the exact screen sizes of various devices, we want to make our layout work at every single resolution. In this way we become device independent.

To achieve this we will simply introduce breakpoints at arbitrary widths when the layout becomes too squashed to be comfortably readable, and modify the columns to make the content presentable again.

In the case of this layout, we'll add the following media queries:

article {
	.Cols ( @colspan: 1; @total_cols: 3; @padding: 0 20 * @toRems 20 * @toRems 20 * @toRems; @margin_bottom: 20 * @toRems;  @gutter: 2; );
	background-color: #eee;
	@media (min-width: 68rem) {
		&:nth-of-type(3n) {
			margin-right: 0;
		}
	}
}

@media (max-width: 68rem) {
	article {
		.Cols ( @colspan: 1; @total_cols: 2; @padding: 0 20 * @toRems 20 * @toRems 20 * @toRems; @margin_bottom: 20 * @toRems;  @gutter: 2; );
		@media (min-width: 53rem) {
			&:nth-of-type(2n) {
				margin-right: 0;
			}
		}
	}
}

@media (max-width: 53rem) {
	article {
		.Cols ( @colspan: 1; @total_cols: 1; @padding: 0 20 * @toRems 20 * @toRems 20 * @toRems; @margin_bottom: 20 * @toRems;  @gutter: 0; );
	}
	section {
		.Cols ( @colspan: 2; @total_cols: 3; @padding: 10 * @toRems 20 * @toRems; @gutter: 1; );
	}
	aside {
		.Cols ( @colspan: 1; @total_cols: 3; @padding: 10 * @toRems 20 * @toRems; @gutter: 1; @edge: true; );
	}
}

@media (max-width: 36rem) {
	section {
		.Cols ( @colspan: 1; @total_cols: 1; @padding: 10 * @toRems 20 * @toRems; @edge: true; );
	}
	aside {
		.Cols ( @colspan: 1; @total_cols: 1; @padding: 10 * @toRems 20 * @toRems; @edge: true; );
	}
}

As mentioned above, breakpoints are determined on a case-by-case basis for each design.

For this purpose I use the Firefox plugin Firesizer to give me a display of how wide my browser window is in pixels. I gradually shrink the browser width and when I identify a point that is too cramped I make a note of the display width.

Unfortunately LESS will not allow any operations to occur on the values we're using for our media queries, so we're unable to do something like @media (max-width: 1000 * @toRems).

Because the @toRems variable can't be used here to convert the pixel value, I instead convert the width to ems using http://pxtoem.com/

After converting the identified width to rems we add a breakpoint at that width and apply a new .Cols() mixin to the element. This can change the colspan value and / or the totalcols value depending on what looks best at that width.

Where required, we're also wrapping each nth-of-type() selector in a min-width based media query. This ensures that when the layout gets smaller and the number of columns reduces, the edge column is correctly identified as every second one instead of every third.

For example, at the first breakpoint of 68rem I change the articles from each being one of three columns, to being one of two columns.

I also add a min-width: 68rem media query around the :nth-of-type(3n) selector so it applies only at sizes greater than 68rem.

Then I add a new :nth-of-type(2n) selector to our media query for sizes lower than 68rem, that will identify every second post as needing no gutter.

These changes create the following layout when the viewport is smaller than 68rem wide:

classiclayout_2cols

At the next breakpoint of 53rem I reduce the number of article columns again down to a single column, i.e. @colspan 1 of @total_cols 1, with each article stacked on top of the other.

Again I add a min-width: 53rem media query around the nth-of-type(2n) selector we added above, so it no longer applies when our articles reduce to one of one columns.

I also change the section width from 3 / 4 columns to 2 / 3, and I change the aside width from 1 / 4 width to 1 / 3. This allows a little more space for the sidebar so it's not too squashed at this width.

Now when the design is reduced to less than 53rem in width it looks like this:

classiclayout_singlearticlecol

Finally, at 36rem when the layout becomes too narrow to accommodate multiple columns at all I change both the section and aside elements down to 1 / 1 columns, which pushes the aside down below the section element.

The layout now looks like this at any width less than 36rem:

classiclayout_collapsed

Wrapping Up

We've gone through a lot of detail above on how the .Row() and .Cols() mixins work, in order to give you a full understanding so you can make even further additions or modifications to them if you choose. However, the end result of everything we've covered is actually a super-simple and easy to use layout system.

Through using just two mixins, you can now create highly complex, yet flexible and scalable layouts with virtually no restrictions, outputting only the CSS you need, and using highly semantic markup.

You can use any number of total columns you want for your grid, any width of individual columns and any width of gutters. Plus, your grids are infinitely nestable.

Download your own copy of the mixins written for this tutorial and try them out for your next project layout!

Related Posts