Nate Northway

Collapsing Content

blog | October 6, 2016

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 (max-width: 1400px)
  and (min-width: 575px) {
    //medium desktop, browser sized 575px to 1400px
  }

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. What? I haven't talked about max-width yet?

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 it's 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-bottom
row-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 container
flex-end: items are pushed towards the end of the container
center: items are centered along the line
space-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 line
flex-end: cross-end margin edge of the items is placed on the cross-end line
center: items are centered on the cross-axis
baseline: items are aligned according to their baselines
stretch: 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 container
flex-end: lines pushed to the end of the container
center: lines packed to the center of the container
space-between: lines evenly distributed, with the first pushed to the start of the container and the last pushed to the end of the container
space-around: lines evenly distributed, with equal space around each line
stretch: 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 line
flex-end: cross-end margin edge of the item is placed on the cross-end line
center: item is centered on the cross-axis
baseline: item is aligned according to their baselines
stretch: 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 it's 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: < 768px
Small: >= 768px
Medium: >= 992px
Large: >= 1200px

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.

Reply

Your email address will not be published.All Fields Are Required

< Previous Post: Mobile MenusNext Post: Borders, Pseudo Elements, and Folds >