Content Folding

Published by Chris Coyier

Less than a year ago, Trent Walton published Content Choreography in which he lamented at some of the difficulties and limitations of responsive layouts.

At times, it seems as though all of the site architecture & planning goes out the window as content reflows.

You have to admit, an awful lot of responsive designs end up being:

  1. Squeeze everything into one column
  2. Push the sidebar(s) to the bottom

*cough* this site does that *cough*

Trent mentioned it may be a more appropriate approach to "interdigitate" content.


Interdigitate

That is, to fold bits of content together into that single column in a more thoughtful or useful manner.

A Practical Example: Ad Folding

Consider a layout at a large browser window size. Two columns. Articles in a wide column on the left, advertisements in a narrow column on the right.

At narrower browser window sizes we've decided to drop down to a single column. This layout is probably done with floats, the most common layout method still today. Unfortunately that probably means setting both columns to 100% wide and letting their source-order determine which is on top. Meaning: push all the ads to the bottom. Probably not ideal. It likely would be nicer to fold the ads into the content.

But how?

There's probably a number of ways to do this. With JavaScript, you could measure the window width and shuffle things around in the DOM. That seems heavy to me, but the browser support would be good. I'd rather lean on CSS for this kind of thing. That's what CSS is (supposed to be) for. The grid layout may hold some possibilities for us, but for this tutorial, let's use the hot-off-the-presses CSS Regions, a contribution by Adobe.

Fair warning: This stuff is super new and subject to change. When I wrote this in March 2012, this demo worked in Chrome. Now in January 2013, it doesn't anymore. Chrome 15-18 had partial support and then it was pulled in 19, although Chrome still reports support of the property.

HTML

<section class="main-content">

  <article> ... </article>

  <div class="ad-region-1">
    <!-- empty, ads flow into here as needed -->
  </div>

  <article> ... </article>

  <div class="ad-region-2">
    <!-- empty, ads flow into here as needed -->
  </div>

  <article> ... </article>

  <div class="ad-region-3">
    <!-- empty, ads flow into here as needed -->
  </div>

</section>

<aside>
   <!-- Fallback content in this flow region, probably clone #ad-source -->
</aside>

<!-- Source of flowing content, essentially hidden as an element -->
<div id="ad-source">
  <a href="#"><img src="ads/1.jpg"></a>
  <a href="#"><img src="ads/2.jpg"></a>
  <a href="#"><img src="ads/3.jpg"></a>
  <a href="#"><img src="ads/4.png"></a>
</div>

Notice the "content" (our ads) are tucked into a <div> at the bottom of the page. Once we set up the CSS regions, the element will essentially be hidden and the content inside it will flow into the regions we tell it to.

CSS

We kick it off by telling our content-holding div#ad-source to flow it's content into a "named flow":

#ad-source {
  -webkit-flow-into: ads;
  -ms-flow-into: ads;
}

I'm only using two vendor prefixes here because that's the only support for now. I'd recommend not using the unprefixed version as this stuff could change in final implementation.

'ads' is an arbitrary name that we just made up. It could be anything, like naming a CSS animation.

Now we set up regions in which for that content to flow. First, into the aside, then into the interdigitated divs between the articles.

aside, [class^='ad-region'] {
  -webkit-flow-from: ads;
  -ms-flow-from: ads;
}

In our case, at wide browser window widths, the aside is large enough to hold them all. At narrower widths, through media queries, we hide the aside, forcing the content to flow into the divs.

[class^='ad-region'] {
  display: none;
  height: 155px;
  width: 100%; /* Weird that this is required */
}

@media (max-width: 800px) {
  [class^='ad-region'] {
    display: block;
  }
  [class^='ad-region']:last-child {
    height: 300px; /* I wish height auto worked */
  }
  aside {
    display: none;
  }
}

Semantics

Soooo yeah, we have some empty elements we're tossing around here. Not super semantic. I'm not sure what this does for accessibility either. I do know that the DOM doesn't change around. All the content, despite where it "flows", is still within the "source" (the element with the flow-into property).

Here's the thing though: regions are layout style agnostic. We're still using floats for layout in this example, but the layout style you use doen't really matter. Regions paired up with the CSS Grids might be much more powerful and more semantic (I just don't know).

Browser Support

CSS Regions is shipping in Chrome 16 through 20, then put under the flag "Enable CSS Regions" in 21-22, and now under the flag "Enable experimental WebKit features" in Chrome 23+. It works in Safari 5.2 (available as dev, or WebKit Nightly). Supposedly it works in IE 10, but I couldn't get it to.

Demo & Download

For your enjoyment:

View Demo   Download Files

Please note the browser support above, it's very limited.

Also note that at some fairly wide browser window widths, the ads in the aside get cut off. I'm leaving it that way to illustrate how regions don't expand in height naturally, you need to account for that yourself.

Update: This demo worked, then was broken, and now works again. I made it work through Adobe's CSS Regions Polyfill. This will need to be revisited again when the spec and browser support settles down.

Browser Detection

This HTML and CSS as is would be pretty sucky in a browser that didn't support CSS regions. There would be this chunk of ads at the bottom of the page randomly. Instead I chucked in a bit of JavaScript (based on this) to test for it and apply classes to the html element reflecting support.

// Generic Testing Function
var supports = (function() {  
   var div     = document.createElement('div'),  
       vendors = 'Khtml Ms O Moz Webkit'.split(' '),  
       len     = vendors.length;  
  
   return function(prop) {  
      if (prop in div.style) return true;  
  
      prop = prop.replace(/^[a-z]/, function(val) {  
         return val.toUpperCase();  
      });  
  
      while (len--) {  
         if (vendors[len] + prop in div.style) {   
            return true;  
         }  
      }  
      return false;  
   };  
})();  
  
// Test
if ( supports('flowInto') ) { 
   $("html").addClass('cssregions');  
} else {
   $("html").addClass('no-cssregions'); 
}

That was we can do stuff like this to make sure the fallback is OK:

#ad-source {
  display: none;
}
.cssregions #ad-source {
  display: block;
  -webkit-flow-into: ads;
  -ms-flow-into: ads;
}

And also, copy the contents of the div#ad-source into the aside, so at least the ads appear there at the widest browser window widths.

This should work, but it is reporting false positives in Chrome since Chrome removed support for it in Chrome 23. You can turn on support though...
  1. Using the browser bar navigate to chrome://flags
  2. If you're running Chrome 21 or Chrome 22, find the "Enable CSS Regions" flag and enable it
  3. If you're running Chrome 23 or newer, find the "Enable experimental WebKit features" flag and enable it.
  4. Restart your browser by clicking the "Relaunch Now" button on the lower left corner.

If you wanted to add this test to Modernizr you'd do:

Modernizr.addTest('regions', Modernizr.testAllProps('flow-into'));

Hopefully they'll add that as an option in future versions.

Things I Wish Were Better

  • I wish you could style content based on what container element it happens to flow into. That might be problematic as a different style in a different container might cause a reflow which pushes it back out of that container causing infinite looping sadness, but maybe there is some kind of solution for that.
  • I wish block level elements would remain block level even with the flow-from property. It's weird you have to set them to width: 100%;
  • I wish height: auto; worked on the region with the last bit of flowed content. I understand most flow regions can't do that because then they would just expand to fit all the content and not continue the flow, but the final region should be able to know what it has left and expand naturally.
  • I wish you could flow-from and flow-into the same element. That way one semantic element could be the source and only if it shrinks or goes away do the other regions get filled.