The cornerstone of responsive design is collapsing (and expanding) content to fit on differently sized screens. But what’s the best way to do it? There are many ways to accomplish a responsive design. If you use front-end frameworks like Bootstrap or Materialize.css, responsive design is usually built right in. But, if you don’t use frameworks, it’s easy to forget about what looks good across platforms, and it gets hard to test for it.
It’s important to design responsively.
A lot of internet traffic (like, 56%) comes from mobile platforms. So, engaging with your audience means implementing a responsive design. Not only does responsive design ensure that your mobile user base is taken care of, but it also makes your users with large screens feel at home. And, as screens are becoming bigger, higher in pixel density, and easier to attain, it’s becoming just as important to serve those users.
Methods
There are a lot of ways to implement a responsive design. I’ve already mentioned one: using a front-end framework. Bootstrap, Materialize.css, and Foundation all do a wonderful job implementing responsive design out-of-the-box. But, if you’re like me, you like to eliminate learning whatever framework is popular and hand-coding your sites, or, maybe, using your own, custom built set of modules, or even your own framework.
Media Queries
Media Queries are the groundwork of a responsive design. In CSS, they can be used to change the styles of elements depending on screen size, orientation, and media type. Here are a few standard media queries that I use.
@media screen and (max-width: 575px) {
//desktop, browser sized to < 575px
}
@media print {
//printed version of a webpage
}
@media screen and (min-width: 1400px) {
//large desktop
}
@media screen and (min-width: 1220px) {
//desktop
}
@media screen and (min-width: 992px) {
//big tablet, laptop
}
@media screen and (min-width: 768px) {
//tablet in portrait, some phones
}
@media screen and (min-width: 575px) {
//most phones
}
Inside the media query blocks, your declarations change the behavior of the selected elements. If I have an image that is half width on a large desktop that I want full width on a smaller screen, I may have a media query that looks something like this:
img {
width: 50%;
float: left;
}
@media screen and (max-width: 575px) {
img {
width: 100%;
float: none;
}
}
That’s all fine and dandy, but it’s definitely not all that responsive web design is. There are a lot of other tools in this bag.
Percentages over pixels
I’m a strong proponent of using percentage values over absolute values, except in very rare cases, like declaring border width. And using percentages isn’t difficult either. Relative sizes can be calculated easily: target ÷ context = result Proportional sizing encourages responsive design. Let’s dive in a little bit here. Say you’ve got a layout, a .psd from your designer, that calls for a main-container
at 960px
wide, with two containers inside of it, one large-container
at 556px
wide and one small-container
at 331px
wide. If we were content with pixel values, and that everyone had the same size screen, we might do this:
.main-container {
width: 960px;
margin: 0 auto; //centers the page
}
.large-container {
padding: 0 18.25px; //fill out the container widths
width: 556px;
}
.small-container {
padding: 0 18.25px; //fill out the container widths
width: 331px;
}
This will do the job on screens larger than 960px, but, definitely not on screens smaller than that. If we want to make our grid more responsive, we could throw in some calculations. Don’t be scared! Remember, target ÷ context = result. So, 556px ÷ 960px = 0.5791666666666667. Bump up that number by a factor of 100 and you get 57.91666666666667%. That’s the large-container
width. The small-container
width is 331px ÷ 960px = .344791666666667, or 34.4791666666667%. Our content is almost to 100% width (960px). We just need to figure out the padding
of each element. To do that, we take the remaining width of 73px
, divided by 2 to distribute between the two elements, and divided by two again to distribute to either side. That’s a padding
of 18.25px
, or 1.901041666666667%
, or a total combined padding of 7.604166666666667%
, which, when added to our previously calculated element total (92.39583333333337%) equals 100%. Let’s plug these numbers in:
.main-container {
width: 960px;
}
.large-container {
padding: 0 1.901041666666667%;
width: 57.91666666666667%;
}
.small-container {
padding: 0 1.901041666666667%;
width: 34.4791666666667%;
}
Here’s the demo:
See the Pen Percentage over Pixels by Nate Northway (@the_Northway) on CodePen.
So, using percentages over pixels can help, for sure. But that’s just the first step. The next step is knowing when to break that design and go for something else. Using margin: 0 auto
on larger screens helps your site stay centered when it’s reached it’s max-width
, but that doesn’t help on small screens. That’s where max-width
comes in.
Max-width
The max-width
CSS Property is so great, really, it’s one of the best properties out there. Say your designer gets you a .psd designed at 960px. That’s great, that’s fine, you can code that out. But what if people have screens bigger than 960px? Do you change the design to be super wide? max-width
becomes your friend, here.
@media (min-width: 960px) {
#page {
max-width: 960px; //your page is never larger than 960px
margin: 0 auto; //and it's always centered
}
}
Boom. If you want to have the background image/color/other elements go side-to-side of the browser window, the parent element (like <body>
) can always be set to a larger width. Personally, I set my HTML up to account for this, as follows:
<body>
<header>
<div class='container'>...</div>
</header>
<main>
</main>
<footer>
<div class='container'>...</div>
</footer>
</body>
body,
header,
footer {
max-width: 100%;
}
.container,
main {
max-width: 960px;
margin: 0 auto;
}
This way, the navigation, site title, footer links, and all content is always center aligned and, more importantly, on the same grid that everything else exists on. The overflow of the background colors and borders moves to the edge of the screen, making your site look full, rather than clipped and pasted on to the page. max-width
is great, isn’t it?
Ok, so onto collapsing now, yeah?
Using media queries, percentages, and a nugget of knowledge, we can collapse.
Utilizing the above examples, let’s work out some breakpoints. Breakpoints are a point at which the layout of the page changes to allow for better usability and readability. Usually, I make my breakpoints at about 2/3 and 1/2 of the original design. If the original design is 1000px, I’ll have a break point at 660px and another at 500px. The 660px breakpoint will collapse the columns to full-width, and the 500px breakpoint will hide the menu, bring up the navigation icon button, and further collapse any content. Here’s the code, going off of our last example.
@media screen and (max-width: 639.9999936px) {// 960px * 2/3
.large-container,
.small-container {
width: 100%;
}
}
This spreads out the containers to full-width. Now, at the smaller breakpoint, I’ll add in more media queries to further widen content, if needed. For example, articles with floated images. This is what an article with image might look like:
...
<article>
<p>
<img src='example.jpg' />
XOXO glossier fingerstache quinoa. Cardigan migas affogato knausgaard keffiyeh, artisan lomo biodiesel meggings small batch gluten-free salvia tofu pop-up. Lomo cold-pressed aesthetic.
</p>
</article>
...
...
p {
width: 100%;
}
p>img {
float: left;
width: 33.33333333%;
}
So, the image is floated left and has a width of 33% of the parent element, the <p>
tag. This probably looks fine on desktop, and possibly even on tablets, but on mobile, the image will be very small. So, in our media query, we can 1. ‘unfloat’ it, and 2. widen it.
@media (max-width: 480xp) {//960 * .5
p>img {
width: 100%;
}
}
Here’s a demo of that (probably best to view it in CodePen):
See the Pen Responsive Resizing by Nate Northway (@the_Northway) on CodePen.
If that math is too crazy for you, there is another way to do it, which may be more intuitive, and uses more abstract definitions: Flexbox.
Flexbox Is My Hero
I love flexbox. I use it for everything. From <body>
layout to create fixed headers and footers, to using it to display my <nav>
correctly, to using it in articles and for tables, flexbox is awesome. Using it to help with responsive design takes a little bit of knowledge though. I’ll break it down best I can for you here, but I’ll also link to other articles written by people with much greater skill than I.
The Idea
The Flexbox Layout
(as it’s officially known) aims at providing a more efficient way to lay out, align, and distribute space among items in a container, even when their size is unknown and/or dynamic. The primary idea behind it is to give the parent container the ability to change it’s children’s width, height, and/or order to best fill the available space. A flex container shrinks items to prevent overflow or grows/expands them to fill space.
The Basics
The parent, or flex-container, contains all of the flex-items, or children. It has 5 properties.
display
The display
property defines the flex container and enables a flex context for its children.
.container {
display: flex;
}
flex-direction
The flex-direction
property defines an axis and the direction the flex-items are placed in the container. The values can be:row
: default value, left-to-right in ltr
, right-to-left in rtl
column
: top-to-bottomrow-reverse
: right-to-left in ltr
, left-to-right in rtl
column-reverse
: bottom-to-top
.container {
flex-direction: row;
flex-direction: column;
flex-direction: row-reverse;
flex-direction: column-reverse;
}
flex-wrap
The flex-wrap
property defines whether your rows will wrap or not. By default, rows will always try to fit on one line. Setting this property to wrap
avoids overflow. It has three possible values:nowrap
: default value, single-line/left-to-right in ltr
, right-to-left in rtl
wrap
: multi-line/left-to-right in ltr
, right-to-left in rtl
wrap-reverse
: multi-line/right-to-left in ltr
, left-to-right in rtl
.container {
flex-wrap: nowrap;
flex-wrap: wrap;
flex-wrap: wrap-reverse;
}
flex-flow
Shorthand for flex-direction
and flex-wrap
. The first value is any of 4 from the flex-direction
property, and the second value is any of the the 3 from the flex-wrap
property.
.container {
flex-flow: row nowrap;
...
flex-flow: column-reverse wrap-reverse;
}
justify-content
The justify-content
property defines alignment along the main-axis. It helps distribute extra free space left over when either all flex items on a line are inflexible or are flexible but have reached their maximum values. It also has some control over the alignment of items when they overflow the line. It has 5 possible values:flex-start
: default value, items are pushed towards the start of the containerflex-end
: items are pushed towards the end of the containercenter
: items are centered along the linespace-around
: items are evenly distributed along the line with equal space between them.space-between
: items are evenly distributed along the line, with the first item pushed to the start of the container and the last item pushed to the end of the container.
.container {
justify-content: flex-start;
justify-content: flex-end;
justify-content: center;
justify-content: space-around;
justify-content: space-between;
}
align-items
The align-items
property defines the default behavior for how flex-items are laid out along the cross-axis on the current line. It’s like justify-content
for the cross-axis (perpendicular to the main axis). It has 5 possible values:flex-start
: cross-start margin edge of the items is placed on the cross-start lineflex-end
: cross-end margin edge of the items is placed on the cross-end linecenter
: items are centered on the cross-axisbaseline
: items are aligned according to their baselinesstretch
: default, items are stretched to fill the container (still respects min/max-width)
.container {
align-items: flex-start;
align-items: flex-end;
align-items: center;
align-items: baseline;
align-items: stretch;
}
align-content
The align-content
property defines the behavior when there is extra space in the cross axis across multiple lines. It’s like align-items
, but for wrapped lines of content. It has 6 possible values:flex-start
: lines pushed to the start of the containerflex-end
: lines pushed to the end of the containercenter
: lines packed to the center of the containerspace-between
: lines evenly distributed, with the first pushed to the start of the container and the last pushed to the end of the containerspace-around
: lines evenly distributed, with equal space around each linestretch
: default, lines stretch to fill remaining space.
On to the flex-item’s properties.
order
The order
property defines the order in which flex-children are laid out. By default, they are laid out in the source order, however, you may want to change this given different contexts. Any whole integer can be used as a value.
.item {
order: 1;
}
.item {
order: 2;
}
flex-grow
The flex-grow
property defines how much an item can grow if needed. It’s value is unit-less and serves as a proportion which dictates what amount of the available space inside the flex-container the item should take up. If all flex-items have flex-grow
set to 1, remaining space will be distributed evenly among all children. If one of the children has a value of 2, the remaining space would take up twice as much as the others (or try to, at least). The default value is 0
.
.item {
flex-grow: 1;
}
flex-shrink
The flex-shrink
property defines the ability to shrink, if necessary. Whole integers are accepted but negative numbers are not. The default value is 1
.
.item {
flex-shrink: 1;
}
flex-basis
The flex-basis
property defines the default size of an element before remaining space is distributed. It can be a length, like 20%
, 20px
, or 20rem
, or a keyword. The auto
keyword means ‘base this items flex-basis off it’s width or height property’. If set to 0
, the extra space around content isn’t factored in. If set to auto
, the extra space is distributed based on it’s flex-grow
value.
.item {
flex-basis: auto;
}
flex
The flex
property is the shorthand for flex-grow
, flex-shrink
, and flex-basis
. The flex
shorthand should be used as it sets the values of all three properties intelligently.
.item {
flex: 1 1 auto; //flex: <flex-grow> <flex-shrink> <flex-basis>
}
align-self
The align-self
property overrides the the default item alignment or the one specified by align-items
set in the parent element’s declarations. It defines the behavior for how this flex-item is laid out along the cross-axis on the current line.flex-start
: cross-start margin edge of the item is placed on the cross-start lineflex-end
: cross-end margin edge of the item is placed on the cross-end linecenter
: item is centered on the cross-axisbaseline
: item is aligned according to their baselinesstretch
: item is stretched to fill the container (still respect min/max-width)
.item {
align-items: flex-start;
align-items: flex-end;
align-items: center;
align-items: baseline;
align-items: stretch;
}
Putting it all together
So, now we’ve got some flexbox knowledge, some knowledge on percentages, and some knowledge on why we need to make things different sizes on different screens. Let’s put it all together. Let’s assume that I don’t know exactly how wide my main content area and sidebar should be, but I’ve got a good estimate that the .main-content
wrapper should be 3/5 of the screen width, the .sidebar
should be 1/5 of the screen width, and the remaining 1/5 of space should be evenly distributed. We can use flexbox to do this job!
<div class='wrapper'>
<div class='main-content'>...</div>
<div class='sidebar'>...</div>
</div>
.wrapper {
display: flex;
flex-flow: row nowrap;
align-items: stretch;
justify-content: space-around;
}
.main-content {
width: 60%; // 3/5
flex: 0 1 auto;
}
.sidebar {
width: 20%; // 1/5
flex: 0 1 auto;
}
This sets the width of the items to 60%
and 20%
, respectively. No need to set margin or padding, this is done by assigning the space-around
value to justify-content
in the parent element. Here’s a demo:
See the Pen Simple Flexbox Demo by Nate Northway (@the_Northway) on CodePen.
This is great, and would work fine, but only until you get to the point where your content starts overflowing its containers. This is where things get real fun. We can set a media query to the point where content starts to overflow. In this case, let’s say it’s at 500px
screen width. Inside the media query, we set the width of the flex-items to 100%, the flex-flow
property of the flex-container to column nowrap
, and violia, we have a collapsing system.
See the Pen Simple Flexbox Demo With Collapse by Nate Northway (@the_Northway) on CodePen.
Figuring out where to collapse your system
It takes a bit of trial and error. First off, know that no design system is perfect. Figuring out your breakpoints can be tough. Bootstrap’s grid system is as follows:
Extra-small: < 575px
Small: <= 768px
Medium: <= 992px
Large: <= 1220px
Extra-large: <= 1400px
Double XL: > 1400px
I think that’s a good starting point for figuring out your own breakpoints, but it may not serve you quite as well as others. I get my numbers on a per-project basis. If the design is very complex, using a lot of small elements, my breakpoints are usually numerous and closer together. If the design is simple, two or three columns, I usually have two or three breakpoints, spread apart. It depends on context, is what I’m trying to say, just as anything in web design: context is king.
I use a lot of other sources when I write things. The CSS-Tricks Guide to flexbox is a wonderful reference that I use quite often.
I highly suggest reading Ethan Marcotte’s book, Responsive Web Design. And if you’re a podcast listener, I strongly suggest subscribing to his Responsive Web Design Podcast, as well.