Flexbox – The All-Conquering Hero

Screenshot of Take Me Home James' website demonstrating the use of Flexbox.

I love Flexbox. It really is the perfect solution to all those pesky layout problems of yesteryear. It’s been a welcome member of my web development arsenal for some time now (with appropriate fall-backs in place for those less well-endowed browsers of course), but I’m still frequently amazed by both the power and simplicity at which it goes about its business.

What Is Flexbox?

Flexbox (or ‘Flexible Box Layout Module’ if you’re a fan of syllables) is a method for laying out content on a web page, whereby items can be grouped or distributed within a container much more easily than previous layout solutions workarounds (such as floats and tables). The real selling point over previous methods owes to the fact that items align as intended, even when the dimensions of parent and/or child are unknown, such as with text elements, or dynamic content.

Flexbox Usage

This article isn’t about the syntax employed; there are already some great articles around that do that just fine. Instead, I wanted to cover a real-world situation where I’ve found Flexbox to be a real life-saver, and lay out content in a way that would have previously needed tables, JavaScript, or Voodoo magic.

Case Study 1 – The Content Grid

For Take Me Home James‘ services pages, I created individual blocks of data (or cards), to display the various services that the company offers. Each card features a title, image, paragraph, and table, and whilst most elements (such as the images) are a uniform size, the paragraph text can wrap onto three or four lines, creating cards of varying height.

Image of Take Me Home James' 'Domestic & Pleasure' services page, with and without Flexbox.
With and without Flexbox – note how the differing copy lengths play havock with alignment.

The Problem:

If this was a fixed-width website, perhaps I could have maintained the grid by using the tried-and-tested .row and .column trick (ubiquitous to most layout grid systems), using floats with lashings of clearfix goodness to ‘block out’ three cards at a time. However, due to the responsive nature of the site, columns can be either 1, 2, or 3 cards wide, so the position of .row divs would have to change dependent on the viewport width. Maintainability would also become an issue if a card ever needed to be inserted somewhere into the pack, as the position of .row divs would need to be refactored.

Image of two content boxes on Take Me Home James' 'Domestic & Pleasure' services page, showing the differences in paragrph height.
Paragraph height is not a given, so Flexbox takes care of any variance.

The Solution:

Well, the answer is obvious! I employed the use of Flexbox twice for this layout – once for grid alignment, and once for the content block height.

The HTML was set up as such:

<ul class="feature-boxes">
  <li class="feature-box">
    <div class="feature-title">
      <h2>Evenings Out</h2>
    </div>
    <img src="/img/evenings-out.jpg">
    <p>Out clubbing with friends or an intimate date night with your partner, we'll make sure your evening runs smoothly.</p>
    <div class="feature-info">
      <table>
        <tr>
          <th>Minimum Booking</th>
          <td>2 hours</td>
        </tr>
        <tr>
          <th>Maximum Booking</th>
          <td>4 hours</td>
        </tr>
        <tr>
          <th>Notes</th>
          <td>Consultation available</td>
        </tr>
      </table>
    </div>
  </li>
  <li class="feature-box">
    <div class="feature-title">
      <h2>Race Days</h2>
    </div>
    <img src="/img/race-days.jpg">
    <p>Ascot, Newmarket or Cheltenham, if you fancy a flutter, we're a safe bet to get you there in style!</p>
    <div class="feature-info">
      <table>
        <tr>
          <th>Minimum Booking</th>
          <td>4 hours</td>
        </tr>
        <tr>
          <th>Maximum Booking</th>
          <td>8 hours</td>
        </tr>
        <tr>
          <th>Notes</th>
          <td>Consultation available</td>
        </tr>
      </table>
    </div>
  </li>
  <li class="feature-box">
    <div class="feature-title">
      <h2>Spa Days</h2>
    </div>
    <img src="/img/spa-days.jpg">
    <p>Everyone needs pampering from time to time, so why not unwind with a day of pure pleasure!</p>
    <div class="feature-info">
      <table>
        <tr>
          <th>Minimum Booking</th>
          <td>4 hours</td>
        </tr>
        <tr>
          <th>Maximum Booking</th>
          <td>8 hours</td>
        </tr>
        <tr>
          <th>Notes</th>
          <td>Consultation available</td>
        </tr>
      </table>
    </div>
  </li>
</ul>

The above code sets up the following for the grid:

  • An unordered list with the class .feature-boxes, containing the cards
  • Individual cards with the class .feature-box

And the following for each card:

  • A div containing the title of the box
  • An image
  • A paragraph
  • A div containing a table

Please note: The containing divs were necessary due to formatting requirements (padding around the table, for example), but usually I’d avoid using containing divs wherever possible.

And the Flexbox-specific CSS:

.feature-boxes {
  display: flex;
  flex-flow: row wrap;
  width: 100%;
}
.feature-box {
  display: flex;
  flex-flow: column nowrap;
  width: 33.333333%;
  margin-bottom: 20px;
}
.feature-box img {
  order: -1;
}
.feature-box p {
  flex: 1 0;
}

The above does the following:

  • Initiates Flexbox on .feature-boxes and .feature-box.
  • Sets the .feature-boxes container flex-flow to row wrap (shorthand for flex-direction: row; flex-wrap: wrap;), meaning that content is to be displayed horizontally (in rows), and spill over to subsequent rows if needs be.
  • Sets the .feature-box container flex-flow to column nowrap (shorthand for flex-direction: column; flex-wrap: nowrap;), meaning that content is to be displayed vertically (in columns), and not spill over to subsequent columns.
  • Set the image order to -1, so that their position is shifted above the title (more on that in a bit).
  • Set the parapgraph flex to 1 0, (shorthand for flex-grow: 1; flex-shrink: 0;), so that each row’s paragraphs retain the uniform height required for equal height cards.

As pointed out above, I made use of the order attribute selector, which can shift blocks of content around, depending on their given value. By default, all elements are given an order value of 0, so by giving img tags a negative order value, their position is shifted up above all those with 0 (everything else). My reasoning for doing this owes to the fact that I could make each card more visually pleasing (by positioning the image at the top of each card), but still have the title positioned at the top of each card for screen readers, ensuring good accessibility standards are maintained (as well as being good for SEO as a bonus).

Responsive Considerations

Depending on the device viewport width, the boxes change width and position to provide the best experience at all times. On mobile screens, each box is 100% of the viewport width, so they stack vertically. In this instance, Flexbox is not required to control the grid; I simply use display: block; on the elements. The first instance of display: flex; kicks in at 481px, where .feature-box width is 50%.

The Fallback

Obviously, with Flexbox being such a new addition to the CSS specification, there are certain browsers that aren’t going to take any notice of the new markup. For those, I employed Modernizr to check whether Flexbox is supported, with the following code placed in the page’s head:

Modernizr.load([
  {
    test: Modernizr.flexbox,
    nope: '/js/equalheights.js'
  }
]);

If the browser doesn’t support Flexbox, the following jQuery script is called:

function makeSame() {
  var $sameHeightDivs = $('.feature-box p');
  var maxHeight = 0;
  $sameHeightDivs.each(function() {
    maxHeight = Math.max(maxHeight, $(this).outerHeight());
  });
  $sameHeightDivs.css({ height: maxHeight + 'px' });
}

function checkSize() {
  if ($(window).width() > 480) {
    $('.feature-box p').css({ height: 'auto' });
    makeSame();
  }
  else {
    $('.feature-box p').css({ height: 'auto' });
  }
}

checkSize();

$(window).resize(function() {
  checkSize();
});

This not only sets each of the .feature-box paragraphs to the same height, but also recalculates the maximum height on window resize. This prevents any weirdness if a user maximises/unmaximises their browser’s window.

Leave a Reply

Comments will be approved pending moderation.