Creating a Responsive Grid System with Susy and Breakpoint
The big appeal behind CSS frameworks (like Bootstrap and Foundation) is that they come with a responsive grid system all set and ready to go. The downside is that you have to be content with their column count, the width of their gutters, and all their classes in your markup. What if your project requirements go beyond the defaults of a prefab CSS grid framework? This article will show how you can build and maintain your own unique responsive grids with two Sass libraries: Susy and Breakpoint.
Susy Basics
For a more detailed introduction to using Susy to manage your grids, check out this SitePoint article or this slidedeck. We’ll just hit the high points here so that you have some context for what follows.
All your grid config settings live in a map called $susy
. You can use the key-value pairs in this map to set your max-width (container
), column count (columns
), and gutter width (gutters
).
With Susy, you specify a container element for your grid elements by using the container()
mixin. The selector that includes that mixin will then be assigned the max-width from the container
value in $susy
, centered with left and right margins set to auto
, and given a clearfix.
Each column inside that container is controlled with the span()
mixin. This mixin typically takes three arguments: span (number of parent columns it will fill), layout (number of parent columns if different from default), and location (first/alpha, last/omega, or a column number).
$susy: (
container: 70em,
columns: 12,
gutters: 1/6
);
.main {
@include container;
}
.content {
@include span(8 of 12 first);
}
.sidebar {
@include span(4 of 12 last);
}
If you want to look under the hood of a more detailed Susy grid, here’s a Sassmeister gist for you to play with.
Breakpoint Basics
If you want a detailed look at how to use Breakpoint, a Sass plugin for managing media queries, this SitePoint article should answer most of your questions. Again, we’ll cover some basics for the sake of introduction.
Breakpoint provides a single powerful mixin for handling almost any kind of media query: breakpoint()
. If you pass the mixin a single measurement (like 960px), it will use that number as a min-width media query. If you provide two measurements, it will use them as min-width and max-width values. If you pass a keyword / value pair, you can create any media query you need: height, orientation, resolution, etc. You can also pass several keyword / value pairs to create complex queries: just enclose each pair in parentheses:
.element {
font-size: 1.3em;
@include breakpoint((min-width 30em) (max-width 60em) (orientation portrait)) {
font-size: 1.5em;
}
}
Mixin: susy-breakpoint()
Now that we’ve got the basics of Susy & Breakpoint, let’s look at how they work together to help us quickly build responsive grid layouts. If you have both imported into your project, you can use the susy-breakpoint()
mixin to combine their functions.
The susy-breakpoint()
mixin wraps Breakpoint’s media queries around Susy’s grid output. It requires two parameters (and takes an optional third parameter).
The first parameter you’d pass to susy-breakpoint()
is the media query data you plan to pass to Breakpoint. This data is passed straight to the Breakpoint mixin and used to generate the media query for this part of your code.
The second parameter is the Susy grid layout: at its simplest, it can be a number: a column count. You can also pass it a map that contains the same keys as $susy
. This column count sets the “context” for everything that Susy calculates inside this mixin. For example, if you pass “16” as the layout, all the Susy grid mixins and functions inside susy-breakpoint()
will use 16 as the column count value, regardless of what columns
value is in the $susy
config map.
If you’re familiar with the concept of scoped variables, this is simply a way to scope a new column count to the current media query. In fact, if you pass a map that follows the $susy
pattern, any values in that map will be scoped to the generated media query context.
The third (optional) parameter for susy-breakpoint()
is Breakpoint’s no-query value. This parameter allows you to handle fallbacks for browsers that don’t support media queries. If you enable $breakpoint-no-query-fallbacks
and pass a class as this third parameter, it will be used as a wrapper class around the current selector (instead of a media query).
$breakpoint-no-query-fallbacks: true;
.element {
color: blue;
@include susy-breakpoint(480px, $susy, '.no-mq') {
color: red;
}
}
// output
.element {
color: blue;
}
@media screen and (min-width: 480px) {
.element {
color: red;
}
}
.no-mq .element {
color: red;
}
Note: the example above doesn’t use Susy grid mixins inside the query: it’s just demonstrating how to use Breakpoint’s no-query.
One Container Grid with Changing Column Elements
There are two ways to manage responsive grids. You’re probably most familiar with the first one: all column measurements are based on the same column count and elements change span count at various breakpoints. This is the way nearly every CSS grid framework operates. Let’s look at how we can create that with Susy and Breakpoint.
The first thing we need is our $susy
map and a couple of breakpoint variables. We’ll also turn on Breakpoint’s no-query feature.
$susy: (
container: 64em,
columns: 12,
gutters: 1/6
);
$bp-medium: 32em;
$bp-large: 60em;
$breakpoint-no-query-fallbacks: true;
After our map is set up, we’ll declare our container element:
.container {
@include container;
}
Inside that container, we’ll set up our two child elements as full-width spans. This will be our default for small screens: the “columns” are stacked vertically.
.content {
@include span(12 last);
}
.sidebar {
@include span(12 last);
}
Let’s move up to the medium and large breakpoints. We’ll do that with the susy-breakpoint()
mixin. We’re passing $susy
as the layout parameter. We could also use 12
(the columns
value in $susy
). I know, this may feel a bit redundant now, but later we’ll see how useful passing a new map as the layout can be.
.content {
@include span(12 last);
@include susy-breakpoint($bp-medium, $susy) {
@include span(6 first);
}
@include susy-breakpoint($bp-large, $susy) {
@include span(8 first);
}
}
.sidebar {
@include span(12 last);
@include susy-breakpoint($bp-medium, $susy) {
@include span(6 last);
}
@include susy-breakpoint($bp-large, $susy) {
@include span(4 last);
}
}
Now we’ve got a basic responsive grid. On any screen smaller than 32em, the content and the sidebar will be full-width and stacked vertically. At 32em, those elements will appear side-by-side at half the total width. At 60em, the ratio between their widths shifts from 1/1 to 2/1. At 64em, the container will stop expanding and will be centered inside the viewport.
Changing Container Grid with Stable Column Spans
The other (and admittedly, more complicated) way to set up responsive grids is to set fixed span values for the columns and change the container column properties. To do this, you’ll need to change the column ratios at each breakpoint by using a column list in the columns
value of a $susy
-style maps for each breakpoint:
$susy: (
container: 70em,
columns: 1,
gutters: 1/16
);
$medium: (
columns: 1 1
);
$large: (
columns: 2 1
);
$bp-medium: 32em;
$bp-large: 60em;
Putting a list of numbers in the columns
value tells Susy to create columns widths that match the proportions of those numbers. The span()
mixin will size then elements to match those proportions based on their location. So columns: 1 1
creates two equal width columns (just like columns: 2
would do), while columns: 2 1
creates two columns and makes the first twice as wide as the second.
To use these ratios, we’d call susy-breakpoint()
and span()
in the following way:
.container {
@include container;
}
.primary {
@include span(1 at 1);
@include susy-breakpoint($bp-medium, $medium) {
@include span(1 at 1);
}
@include susy-breakpoint($bp-large, $large) {
@include span(1 at 1);
}
}
.secondary {
@include span(1 at 1);
@include susy-breakpoint($bp-medium, $medium) {
@include span(1 at 2);
}
@include susy-breakpoint($bp-large, $large) {
@include span(1 at 2);
}
}
The at
location provides the magic here: in the large breakpoint, the first column is wider than the second, so specifying the location tells Susy to make .primary
the width of the first number in the columns
list. Specifying that the .secondary
element is at location 2
tells Susy that it should match the ratio of the second number in the columns
list (and that it’s the last element and should have its margin-right
removed). We have to repeat the span()
mixin inside each susy-breakpoint()
because Susy needs the new columns
measurements to recalculate the width percentages at each breakpoint. Try this out in real life with these Sassmeister gists: one, two.
Conclusion
Susy and Breakpoint make a pretty powerful team here. Not only can you create grids that are precisely as simple or as complex as you need them to be, you can also change those grids as much as your project requires at any breakpoint. Please ask any follow-up questions you have in the comments, or leave a comment to share a clever grid you’ve created with Susy and Breakpoint!