Rethinking the wehkamp.com grid

Jochem Rebergen
Oct 5, 2015 · 9 min read

Using the Sass flexibility to serve the business.

One stable grid

When we began scaffolding the grid-system for the wehkamp.com interface, we started out with the same basic principle Twitter used with Bootstrap for their responsive grid: a fairly simple, twelve-column structure to accommodate a wide range of viewports. Columns could live in rows, and were housed in a central container spacing unit. Although we could’ve implemented the whole Bootstrap library, we chose to start with only their methodology and start writing a lightweight, scalable User Interface kit ourselves without all the bloat that comes with an existing UI framework.

At wehkamp.com, everything revolves around showcasing our products to our visitors in the best possible way, which of course depends on a robust yet flexible grid. On our search and category pages, we have a vertical navigation tree and filtering on the left hand side from which you drill down your search and view the products on the right, three in a row.

Image for post
Image for post
The first version of the desktop view for the search results ~ 1280px

At the largest supported width (1280 pixels — mostly displayed on desktop-like screens), the twelve column structure provides four sections covering three columns each: one section for the navigation tree and three sections for the products and other features like sorting, swap view and paging.

<div class="container">
<div class="row">
<div class="col-md-span3 nav-section">
<!-- nav component -->
</div>
<div class="col-md-span9 product-section">
<div class="row">
<div class="col-md-span4">
<!-- product -->
</div>
<div class="col-md-span4">
<!-- product -->
</div>
<div class="col-md-span4">
<!-- product -->
</div>
</div>
</div>
</div>
</div>

Nothing really fancy here. Everyone who worked with Bootstrap before can relate to the code, which was one of the main advantages when working with several teams in the same codebase.

col-” as component identifier, “md-” as breakpoint handler, and “span* ” to handle the eventual width being adopted.

Rethink your code

As a customer
I want to see more products in a row
So I can get a better glance at the catalog

So basically it meant we had to add an extra product in to each row. This way products were shown big enough on desktop view but still had the perfect dimension on smaller devices. Sure, we could have changed the product width in the product section alone, from it’s original 33% to 25% and be done with it. But that also meant that the widths of the product columns in relation to the navigational column were not in sync.

In addition, when you stack other components below the productlist like ‘Last viewed products’ or general product recommendations which span the total container width, an unwanted misalignment would give the page a messy look & feel.

Grid wireframing

So to make things more clear, we made three sketches to visualize the change before we started altering all templates in the application: one which represents the current situation as a baseline, one that could be done with our current grid setup — but with its drawbacks, and one that what turned out to be the one that we build eventually: Multi-gridded!

Image for post
Image for post
Original column setup

The original setup where product columns as well as navigational sections span equal widths.

Image for post
Image for post
Unwanted quick-win

The most easy way to get the user story done: rename the product column css-class from col-xl-span4 to col-xl-span3 where needed, producing an unwanted side-effect: a global difference between columns and components.

Image for post
Image for post
Eventual column setup

The eventual column setup with the new, flexible multi-width-column grid-system, providing every possible solution while remaining consistent across the whole website.

Getting Sassy with it!

Setup basic variables

$width: 0;
$width-xs: 480px !default;
$width-sm: 640px !default;
$width-md: 768px !default;
$width-lg: 992px !default;
$width-xl: 1280px !default;

Thanks to the !default flag, a you can define an own $width variable before the library includes without seeing your value redefined.

Define grid types

$grid-types: (
10,
12
);

The setup is very easy to extend for when new grid-types should be created.

Arrayify labels and widths

$breakpoints: (
"" $width,
"xs" $width-xs,
"sm" $width-sm,
"md" $width-md,
"lg" $width-lg,
"xl" $width-xl
);

Creating arrays in Sass is very straight-forward. You can use nested lists without braces using different separators to distinguish levels, and add aliases to them in your loops and functions later on.

The media-query mixin

/* Breakpoint mixin */
@mixin
breakpoint($width, $type: min-width) {
@if (str-index($type, max-)) {
$width: $width - 1;
}
@media screen and ($type: $width) {
@content;
}
}
/* Usage */
.nav {
padding: $base-spacing;
@include breakpoint($width-md) {
padding: $base-double-spacing;
}
}

Most of the time we only use this to define things mobile-first, where your notation represents a specific width and up, but optionally you can add a second parameter to define a max-width where the ‘minus one’ is automatically calculated. The @content takes care of all declarations being injected in the new media-query.

The grid generator

@each $breakpoint in $breakpoints {
$alias: nth($breakpoint, 1);
$width: nth($breakpoint, 2);
}

First, we loop over the breakpoints array. This is the first handler we encounter after the “col-”. Within this array, two parameters are set, so we can access those by defining an alias. The nth(var, 1) notation represents the first parameter within the first level.

@include breakpoint($width) {
}

Second, we implement the breakpoint mixin so we can parse the $width from the loop over every breakpoint handler.

@each $grid-type in $grid-types {
}

Third, we loop over the grid-types. In our case, this is the 10 and 12 factor we declared earlier. This array can easily be extended as new widths could be designed in the future.

$breakpoint-handler: if( $alias != "", $alias + "-" , "" );

If an alias is empty — from 0 to the first breakpoint-handler xs, no additional dash for readability has to be inserted so we add an extra if-statement to evaluate this. The $breakpoint-handler takes the $alias we defined earlier and puts this in the eventual column declaration.

@for $i from 1 through $grid-type {
}

To generate the correct amount of columns per grid-type per breakpoint segment, a for-loop with $grid-type as max value for the amount of columns is the ideal candidate. The $i variable from the iterator can be used as index for each column and as a multiplier in the width property.

.col-#{$breakpoint-handler}#{$i}-#{$grid-type} {
width: #{(100 / $grid-type * $i) + '%'};
}
/* without breakpoint handler */
.col-1-10 {
width: 10%;
}
/* with breakpoint handler */
.col-xs-1-10 {
width: 10%
}

When all loops and aliases are in place, we can actually begin to write some lines that eventually render to real CSS. The main selector handles the width, as were the additional selectors are responsible for pulling, pushing or offsetting whitespace for flexibility.
We use the Sass interpolation notation #{ } to make sure everything is calculated before it is finally rendered in to CSS.

The final function

@each $breakpoint in $breakpoints {
$alias: nth($breakpoint, 1);
$width: nth($breakpoint, 2);

@include breakpoint($width) {
@each $grid-type in $grid-types {
$breakpoint-handler: if( $alias != "", $alias + "-" , "" );

@for $i from 1 through $grid-type {
.col-#{$breakpoint-handler}#{$i}-#{$grid-type} {
width: #{(100 / $grid-type * $i) + '%'};
}
.col-#{$breakpoint-handler}push#{$i}-#{$grid-type} {
left: #{(100 / $grid-type * $i) + '%'};
}
.col-#{$breakpoint-handler}pull#{$i}-#{$grid-type} {
right: #{(100 / $grid-type * $i) + '%'};
}
.col-#{$breakpoint-handler}offset#{$i}-#{$grid-type} {
margin-left: #{(100 / $grid-type * $i) + '%'};
}
}
}
}
}

The column properties are an addition to the general properties declared in the base column selector before the grid generator function:

*[class*="col-"] {
float: left;
min-height: 1px;
padding-left: $base-grid-spacing;
padding-right: $base-grid-spacing;
position: relative;
width: 100%;
}

Sprint demo

Image for post
Image for post
Five equal columns in the new version of the desktop view for the search results ~ 1280px

Before
Fixed twelve-column structure only.

<div class="col-md-span4">One Third from 768px and up</div>

After
Self-explanatory multi-column structure. Scalable, maintainable, simple.

<div class="col-md-4-12">One Third from 768px and up</div>
<div class="col-xl-2-10">One Fifth from 1280px and up</div>

There you have it. A compact and extendable Sass function driven by two simple arrays and one list of width variables with a very flexible outcome: a robust and future-proof grid-system which can easily be extended when needed. Happy stakeholders, happy developers.

And some nice story points to add to our Jira burn down chart too.

This entry was originally posted on wehkamplabs.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store