The Sass Mixins/Placeholders I Can’t Live Without for Responsive Web Design
Here at Sift Science, we just completed another big step in our ongoing marketing site redesign, overhauling the homepage and replacing old landing pages with prettier, responsive, and more performant ones. While the big performance improvements aren’t quite ready to showcase yet (check in soon for more on that), I realized that there are a few custom Sass mixins and placeholders that I rely on heavily for responsive development—I’m not actually sure what I’d do without them—and I thought I’d share them here along with some CodePens so that other people might also take advantage of them!
But first, a brief primer: a couple years ago, Responsive Web Design (RWD) was just becoming a thing, brands were just figuring out that their customers were actually shopping on their phones, and you really had to convince people that the extra development time was actually worth it. The first such project I was on (not at Sift) required us to deliver code that looked good at three different breakpoints: 480px, 780px, and 1200px. I probably don’t need to argue that that approach doesn’t fly anymore—these days, a responsive page needs to look good at all screen sizes, from 320px on up.
With that in mind, let’s look at a few common RWD issues and how we can address them elegantly with a Sass mixin or placeholder. We’ll assume, for the sake of this post, that we have to support IE9, so flexbox isn’t really an option. (For the record, the solutions I’ll present here will also work in IE8. And IE7 if we replace pseudo-elements with less-semantic <span>
elements.)
Problem 1: Keeping text vertically centered at all screen sizes
Imagine we have a module (where a module is a block of reusable markup on a page) with a title and subtitle that should both be vertically centered. Let’s also say for now that the module has a fixed height across all screen sizes. Simply adding a margin or padding-top
property à la five years ago for a fixed-width page no longer works, because eventually, as the width shrinks, our text will wrap and no longer be centered:
// bad. old. .my-module { background: #DDE9F7; height: 250px; padding-top: 100px; .my-module-content { margin: 0 auto; text-align: center; width: 75%; h1 { color: #B73207; } h3 { color: #87A4CC; } } }
We can see from the CodePen below that this looks fine on wide screens but quickly breaks down as the screen size shrinks.
Note that you’ll likely want to click on the ‘Edit on Codepen’ links for each example so that you can resize the browser window over and over again to see how each one changes across different screen widths. Because, let’s be honest, that’s the best part about Responsive Web Design anyway.
See the Pen wagmOo by Noah Grant (@noahgrant) on CodePen.
We can remedy this with the following placeholder, which adds a pseudo element of 100% height, inline-block display type, and middle vertical-alignment. I very creatively call it %mid-aligned-pseudo
. Since pseudo elements are children, adding it to the parent .my-module
class makes it a sibling of .my-module-content
, and its full-module height gives .my-module-content
a basis with which to middle-align itself:
// better! %mid-aligned-pseudo { &:before { @include inline-block; // bourbon/compass mixin, sets vertical-align to middle by default; content: ''; height: 100%; } } .my-module { @extend %mid-aligned-pseudo; text-align: center; .my-module-content { @include inline-block; width: 75%; h1 { color: #B73207; } h3 { color: #87A4CC; } } }
Go ahead and stretch/shrink the window of the CodePen. Look how the text stays nice and centered even when it wraps!
See the Pen Better RWD vertical centering by Noah Grant (@noahgrant) on CodePen.
Problem 2: Keeping text vertically centered at all screen sizes when its container’s height is not fixed
Building on what we did for problem 1, let’s now assume that we don’t want to have a set module height, which, for RWD, is pretty limiting anyway. A common example is a background image with a headline/sub-headline that sits vertically centered on top of it. In this case, the background image scales and sets the module’s height appropriately. But that means that the height is different depending on our screen size, and in each case we still want the copy to be centered.
Usually, what is constant here is the images aspect ratio. Conveniently, then, we can use the ol’ padding-top percentage trick to set the height, and then do everything else the same as before. If you haven’t seen this before, padding and margin percentages are always based on the container’s width; thus, setting an element’s -top
or -bottom
property to a desired aspect ratio will exactly equal its height:
AR = height / width => height = AR * width (assuming width of 100%, or 1.00) => height = AR
Since each module or image might have a different aspect ratio, we’ll use a mixin here:
@mixin image-spacer($ar: 1.00) { &:before { content: ''; display: inline-block; padding-top: $ar * 100%; vertical-align: middle; } } .my-module { @include image-spacer(0.5); background: url('path/to/img') no-repeat center center; background-size: cover; // ^^^^ IE >=9 to use 'cover', but it's not necessary here // ... }
Here’s an example with an image from my favorite coffee roaster, which happens to be 653px x 434px, or a 66.5% aspect ratio. The image is placed in the background, and the height of the container is maintained by the pseudo-element. (The image can alternatively be placed inline and absolutely positioned, which I tend to lean towards so that I can use Scott Jehl’s picturefill polyfill and more easily control when the images are loaded. But that’s for another post.)
See the Pen RWD vertical centering variable height by Noah Grant (@noahgrant) on CodePen.
We can take this just a bit further, too, since at some point we might not want the container div to grow in height anymore (ie, an image with a unity aspect ratio would grow its container to 1200px high at 1200px width—probably less than ideal). Let’s say that we want to keep the module from our previous example to 450px high. At the correlated screen width, we’ll switch over to using the solution we used in Problem 1:
@mixin image-spacer($ar: 1.00, $max-height: 600px) { &:before { content: ''; display: inline-block; padding-top: $ar * 100%; vertical-align: middle; } @media (min-width: ($max-height / $ar) { height: $max-height; &:before { padding-top: 0; height: 100%; } } } .my-module { @include image-spacer(.665, 450px); // ... }
Check it out—at a screen width greater than 450 / .665 = 676px
, the height stays constant, and below that it shrinks proportionally with the image, all while the text remains vertically-centered!
See the Pen RWD vertical centering variable height with max-width and container-width by Noah Grant (@noahgrant) on CodePen.
Note that the mixin in the pen also covers the cases where the container is not the full width of the screen by adding an additional $container-width
parameter:
@mixin image-spacer($ar: 1.00, $max-height: 600px, $container-width: 1.00) { &:before { content: ''; display: inline-block; padding-top: $ar * 100%; vertical-align: middle; } @media (min-width: ($max-height / ($container-width * $ar))) { height: $max-height; &:before { padding-top: 0; height: 100%; } } } .my-module { // if the container takes 50% of the screen width... @include image-spacer(.665, 450px, .5); // ... }
For completeness, here’s the final version we use at Sift Science, which includes an optional $min-height
parameter. ($min-height
works great with a background image, but might cause some issues with an inline/absolutely-positioned image whose aspect-ratio must be strictly adhered to.)
Problem 3: Aligning sibling elements with equal width but variable inner-spacing and not using a &$!@#-ing grid system
I hate grids. I think they’re the worst. They make websites look boxy and banal. Also, when used in RWD, they necessarily make each column a variable width, which is often uncalled for. It’d be much nicer if our elements stayed at the width we want (say, 200px), and only the margin between them was flexible. And if/when our columns didn’t fit in their container, they would flow over naturally into another row—no dubious .row
wrapper element necessary*.
Enter the text-align: justify trick. Reading this post a couple years ago changed my (professional) life. The browser essentially treats each child element as a word of justified text (think of a newspaper article where all words are spaced to fit edge to edge inside of a column). We can conveniently put the necessary code into a placeholder:
*flexbox makes this behavior trivial
// remember to add widths to the children! %text-align-justify-trick { font-size: 0.1px; text-align: justify; &:after { @include inline-block; content: ''; width: 100%; } > * { @include inline-block; font-size: 16px; } }
A couple notes:
- A space between each child element is essential here to create the “word break.” If you’re using a markup prepocessor that minifies the output, you might have to manually add a
. - Counter-intuitively, perhaps, you still need to have that odd
font-size: 0.1px
to minimize the default white-space inserted between inline-block elements (0px works for most browsers but you need 0.1 for IE). - Don’t forget: you still need to set the widths on the children!
- Also don’t forget: you might need ghost elements!
- This technique also works really well for creating responsive navigation. Check ours out at siftscience.com for an example.
Check it out in action:
See the Pen text-align justify trick by Noah Grant (@noahgrant) on CodePen.
There are several other mixins, placeholders, and other tips that regularly make my RWD life easier, but if I were stranded on an island and could only take three Sass helpers with me, these would be them. If you have thoughts, improvements, or other favorite helper mixins and placeholders, please leave them in the comments!
These are the kinds of cool projects and innovative solutions that our engineers work on every day. Interested in fighting digital fraud with the rest of the Sift Scientists? Join our team!