A step-by-step guide for working through the process of building a reusable Sass mixin.
Part one of a two-part article: Working Through a Mixin: Part 2
The Back Story
One of the UI design patterns that I often come across is a series of elements or components that sit next to and on top of one another to form a grid-like pattern—think several shopping cart products each with an image, title, and description. Or something that basically looks like this:
See the Pen Sass Component Layout Calculator Mixin by Ryan Blyth (@ryanblyth) on CodePen.
Each time I write the CSS for this layout pattern, I need to do a series of steps to figure out the appropriate component width, margin values, and often make small adjustments based on client or designer feedback. To solve this I have also leveraged grid framewroks to do some of the math, but still find that works well with the grid does not always look and feel just right in the context of the page. Working through this pattern time and time again, I decided to solve the poblem with a reusable mixin suitable for a wide variety of scenarios
A couple things to note before we dive into any code. This mixin provides a grid-like pattern, however it is not meant to be a grid framework like Susy, Neat, or Singularity. It is simply a helper mixin for a common web design pattern. Also, please note that I am using box-sizing: border-box for the CSS box model. http://www.paulirish.com/2012/box-sizing-border-box-ftw. With that out of the way, let’s get to the code. We’ll start with the easiest parts and work to the more difficult tasks, making small gains along the way.
Some Basic CSS
Using float and margins we can get our components lined up.
.inner-component {
float: left;
width: 32%;
height: 150px;
margin: 0 1% 1% 0;
background-color: #565656;
}
Which gives us the following result.
The problem here is we have a right margin on all the elements in the last column, and we have a bottom margin on all the elements in the last row. This makes our grid unbalanced and little funny looking.
We can solve this easily enough with some math and the :nth-child selector to zero-out the right and bottom margins on the elements in the final column and row. First let’s remove the unwanted margins. I won’t go into the details of :nth-child since that is well documented already (http://css-tricks.com/how-nth-child-works), but we can use it to select every third child element as well as the first three elements. And now some math: we know the area we need to fill is 100%, after removing the right margin on every third component we have two 1% margins left over, and we have three components. If we subtract the space our margins occupy we know we need our components to fill 98% of the space; now we have all the figures we need for the math. 98 / 3 = 32.66667. Let’s add the CSS to remove the margins and set our component width to the new known value. We will also apply the vertical margins to the top margin instead of the bottom margin, since we can target the first three elements with :nth-child and set them to zero.
.inner-component {
float: left;
width: 32.66667%;
height: 150px;
margin: 1% 1% 0 0;
background-color: #565656;
}
.inner-component:nth-child(3n) {
margin-right: 0;
}
.inner-component:nth-child(-n+3) {
margin-top: 0;
}
At this point if you only need to use this grid pattern once in your life, you are done. However, this is a common pattern that is used and modified frequently. What if you need four components in a row, or your margins need to be slightly wider, or the grid needs to be responsive at different screen widths, and you need one component per row at narrow screen widths, two at medium, and three at large? And then you also need to do the same thing in sidebar, but on a smaller scale. In that case you have a lot of math and CSS to write (or re-write). Unless you write a piece of code that will handle that re-writing for you.
Enter the Sass Mixin
Since we will be using Sass (specifically SCSS) let’s start simple, by first nesting our styles.
.inner-component {
float: left;
width: 32.66667%;
height: 150px;
margin: 1% 1% 0 0;
background-color: #565656;
&:nth-child(3n) {
margin-right: 0;
}
&:nth-child(-n+3) {
margin-top: 0;
}
}
Now we need to figure out a way to create code that we can easily modify and reuse while repeating ourselves little as possible (DRY: Don’t Repeat Yourself). We know that we want to be able to change the number of components per row and the width of the margin without having to do the math each time. Let’s start with the number of components per row.
There is already a lot of documentation on Sass mixins, so I won’t go into the details of what a mixin is but you generally know you want to create a mixin when you have a code pattern that you need to reuse and that you potentially need to give new values to each time you reuse it. The mixin allows us to do this by working similar to a function in many programming languages: we can create the mixin, assign it parameters, call the mixin, and pass arguments when we use it. If you don’t have much practice with functions and that sounds like gibberish, don’t worry we will be going over what all of that means. For now, here is the mixin as well as the call to the mixin where we will use it on our component selector.
@mixin calc-container {
float: left;
width: 32.66667%;
margin: 1% 1% 0 0;
background-color: #565656;
&:nth-child(3n) {
margin-right: 0;
}
&:nth-child(-n+3) {
margin-top: 0;
}
}
.inner-component {
@include calc-container();
height: 150px;
}
Nothing on screen or in the compiled CSS is different from our previous state, so let’s change it up a bit and add our first parameter, then apply that parameter in our styles, and finally pass a value to that parameter when we call the mixin. First we will create a $components-per-row variable and use that as the first parameter in the mixin. Then we’ll use the value of that gets passed to that parameter to figure out the width of our components as well as our :nth-child values. While we are at it, let’s make sure the height and background color are being applied on the selector where we want to call the mixin and not in the mixin itself, since the height and background could vary from selector to selector.
@mixin calc-container($components-per-row) {
// First let's set some variables and do some math.
$component-width: (100 / $components-per-row) * 1%;
@debug $component-width;
// Now let's set some property values with those variables and do some math
float: left;
width: $component-width;
margin: 1% 1% 0 0;
&:nth-child(#{$components-per-row}n) {
margin-right: 0;
}
&:nth-child(-n+#{$components-per-row}) {
margin-top: 0;
}
}
.inner-component {
@include calc-container(3);
height: 150px;
background-color: #565656;
}
To get the desired width based on the components per row, we need to divide 100 by the desired components per row. So if we want two components, they will each be 50%, three will be 33%, four will be 25%, and so on. When we call our mixin and give it an argument for $components-per-row: @include calc-container(3). That number will be passed to the $components-per-row parameter and we can then use that value to set a new variable called $component-width that will equal 100 divided by 3 to get 33.33333 and then multiplied by 1% to convert our unit-less number to 33.33333%. We will also use that same value in $components-per-row to set out our :nth-child selectors. Notice the $components-per-row variable in the :nth-child selectors are wrapped with the Sass interpolation syntax #{}, this allows us to use variables as part of the selector. See this if you’d like to know more about interpolation: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#interpolation
And the current code now looks like this onscreen.
While it may not be obvious from the onscreen results, we are getting closer to our overall solution. In fact we can use the Sass @debug directive to see the value of the $component-width variable to make sure we are on track.
@mixin calc-container($components-per-row) {
// First let's set some variables and do some math.
$component-width: (100 / $components-per-row) * 1%;
@debug $component-width;
Which gives is the value in our terminal where we are compiling Sass.
sass/_mixins.scss:4 DEBUG: 33.33333%
The $component-width variable value is what we expect. However, the sum of our components and margins is over 100% breaking our layout as we can see in the previous screenshot. So we need a way to set our desired margin thickness and subtract that from the component width. This means we need another parameter on our mixin for margin thickness. Let’s add that parameter, give it a value when we call our mixin, and then subtract that from our component width.
@mixin calc-container($components-per-row, $margin-thickness) {
// First let's set some variables and do some math.
$component-width: (100 / $components-per-row) - $margin-thickness * 1%;
// Now let's set some property values with those variables and math
float: left;
width: $component-width;
margin: 1% 1% 0 0;
&:nth-child(#{$components-per-row}n) {
margin-right: 0;
}
&:nth-child(-n+#{$components-per-row}) {
margin-top: 0;
}
}
.inner-component {
@include calc-container(3, 1);
height: 150px;
background-color: #565656;
}
If we @debug the value of $component-width we have a value of 32.33333%, which is exactly what we want. Now we can change the $component-per-row and $margin-thickness arguments each time we call the mixin and get the correct number of boxes in each row. But if we look at the screenshot, there is still white space on the far-right gutter of our layout. That is because the margins are still “hardcoded” to 1%. We now need to figure out how wide our margins will be to fill in that space between the components with the correct value. However before we can do that, we need to figure out how much space our components are not taking up. First we take our $component-width(32.33333%) and multiply that by our total $components-per-row(3): 32.33333% * 3 = 97%. Now we subtract that value from 100, which is 3%. Let’s make a $remainder-width variable and make it equal to that equation.
@mixin calc-container($components-per-row, $margin-thickness) {
// First let's set some variables and do some math.
$component-width: (100 / $components-per-row) - $margin-thickness * 1%;
$remainder-width: 100 - ($component-width * $components-per-row);
// Now let's set some property values with those variables and math
float: left;
width: $component-width;
margin: 1% 1% 0 0;
background-color: #565656;
&:nth-child(#{$components-per-row}n) {
margin-right: 0;
}
&:nth-child(-n+#{$components-per-row}) {
margin-top: 0;
}
}
.inner-component {
@include calc-container(3, 1);
height: 150px;
background-color: #565656;
}
To calculate the margin width we also need to know how many total margins we’ll have. Since we want the right side of the last column to be flush right, that margin width needs to be 0, which means we need to remove that margin from our calculation. Since we need 1 less margin than components per row, we can set the $total-margin variable to be 1 less the $components-per-row which in this scenario equals 2. We’ll make another variable called $total-margin and make it equal to $components-per-row – 1.
@mixin calc-container($components-per-row, $margin-thickness) {
// First let's set some variables and do some math.
$component-width: (100 / $components-per-row) - $margin-thickness * 1%;
$remainder-width: 100 - ($component-width * $components-per-row);
$total-margin: $components-per-row - 1;
// Now let's set some property values with those variables and math
float: left;
width: $component-width;
margin: 1% 1% 0 0;
&:nth-child(#{$components-per-row}n) {
margin-right: 0;
}
&:nth-child(-n+#{$components-per-row}) {
margin-top: 0;
}
}
.inner-component {
@include calc-container(3, 1);
height: 150px;
background-color: #565656;
}
For our last variable (and our last piece of math) we set the $margin-width to be the $remainder-width divided the total number of margins. So, 3% / 2 = 1.5%. We can take this variable, which we will call $margin-width and assign it to the top and right margin values in our styles.
@mixin calc-container($components-per-row, $margin-thickness) {
// First let's set some variables and do some math.
$component-width: (100 / $components-per-row) - $margin-thickness * 1%;
$remainder-width: 100 - ($component-width * $components-per-row);
$total-margin: $components-per-row - 1;
$margin-width: $remainder-width / $total-margin;
// Now let's set some property values with those variables and math
float: left;
width: $component-width;
margin: $margin-width $margin-width 0 0;
&:nth-child(#{$components-per-row}n) {
margin-right: 0;
}
&:nth-child(-n+#{$components-per-row}) {
margin-top: 0;
}
}
.inner-component {
@include calc-container(3, 1);
height: 150px;
background-color: #565656;
}
And We’re (kind of) Done
Now we have what we want. If we look at the compiled CSS, it looks like this:
.inner-component {
float: left;
width: 32.33333%;
margin: 1.5% 1.5% 0 0;
height: 150px;
background-color: #565656;
}
.inner-component:nth-child(3n) {
margin-right: 0;
}
.inner-component:nth-child(-n+3) {
margin-top: 0;
}
And we can now quickly call the mixin with different values for the arguments. Notice the values do not have to be whole integers.
.inner-component {
@include calc-container(4, 0.875);
height: 150px;
background-color: #565656;
}
Which compiles into this CSS:
.inner-component {
float: left;
width: 24.125%;
margin: 1.16667% 1.16667% 0 0;
height: 150px;
background-color: #565656;
}
.inner-component:nth-child(4n) {
margin-right: 0;
}
.inner-component:nth-child(-n+4) {
margin-top: 0;
}
And looks like this:
We have a reusable mixin that we can apply over-and-over whenever we need to replicate this pattern. However, if we need to use this mixin in a mobile-first approach with media queries, there is a little more work to do. In part two, we’ll go over how we can make this mixin media query ready.