Cross-Browser Flex-Box for Responsive Design

I’m updating this site’s theme to use the flex-box model, (while keeping the theme compatible with browsers that don’t support it yet), and thought I’d share what I learned.

Pre-flex-box layout models create many difficulties,particularly for responsive design. Floats are used for things they were never intended for; it’s hard to make a sidebar reach all the way to the bottom of the page and still be responsive; and building a responsive website without any “hacks” is almost impossible.

The solution… almost

The flex-box model is the solution — almost: browser compatibility isn’t quite there yet. Worse, the spec has been in flux; browsers that do support it have to support three different versions.

Is it realistic today to support three different versions of the flex-box spec on a web-page, and still have fallbacks? I decided to try.

Versions of the spec

There are three major versions of the flex-box spec. Per http://www.caniuse.com and http://beta.caniuse.com, the browser support is as follows:

Flexbox unsupported:
  • IE 9 and prior
  • Opera and Opera Mobile 12 and prior
  • Opera Mini — no version supports flex<-box
2009 Version of Spec: (all prefixed)
  • Chrome 20 and prior
  • Safari and Safari iOS 6.0 and prior
  • Android browser 4.3 and prior
  • Blackberry 7.0
  • Firefox for Android
2011 Version of Spec
  • IE 10 & IE10 Mobile (prefixed)
  • Yes, there is a version of the flex-box model used only by a single version of IE.
2013 Version of Spec
  • IE 11 and later (no prefix)
  • Firefox 27 and (several versions) Prior. (No prefix) does not support flex-wrap
  • Firefox 28 And later flex-wrap support added
  • Chrome 21 thru 28 (prefixed)
  • Chrome 29 and later (no prefix)
  • Safari 6.1 and later (prefixed)
  • Safari Mobile 7.0 and later (prefixed)
  • Opera 15 and 16 (prefixed)
  • Opera 17 and later (no prefix)
  • Android browser 4.0 and later (no prefix)
  • Blackberry 10.0 and later (prefixed)

…And this information will probably be out-of-date by the time you read it. The main point is: for maximum compatibility, we need to support three versions of the spec, one of them in both prefixed and unprefixed forms.

The Modernizr Script

One very useful tool in untangling this mess is the Modernizr script. It allows you to know in the css what features a browser does and does not have.

How does it do this? Add class="no-js" to you <html> element, and then call the script. Provided the browser has javascript activated, it will replace “no-js” with “js”, and numerous other class-names indicating which features a browser supports.

The two that are of most interest to use are:

  • .flexbox / .no-flexbox
  • .flexboxlegacy / .no-flexboxlegacy

Wait… that’s two tests for three versions of the spec, yes?

Yes. Since there is no test for “the flexbox model”, Modernizr has to check for something more specific:

.flexbox
checks support for the 2013 property flex-wrap
.flexboxlegacy
checks support for the 2009 property box-direction

This has results that might be unexpected:

  • IE 10, (using the 2011 spec) has the flex-wrap property, and comes up as .flexbox / .no-flexboxlegacy
  • Firefox 27 implements the 2013 spec, but omit support for flex-wrap — and therefore come up as .no-flexbox / .flexboxlegacy despite otherwise using the most recent version of the spec.

The Sample Page

This isn’t going to win any design awards. The purpose is to demonstrate different ways of using flex-box (or a fallback) to arrange elements horizontally, while making it as clear as possible what’s going on.

For simplicity, I’ll omit media queries and other components that would be used in a production page. This is for demonstration purposes only.

Here is the html for our sample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<code>
<html class="no-js">
<head>
    <title>FlexBox Test</title>
    <link href="flexbox.css" type="text/css" rel="stylesheet"/> 
    <script src="modernizr-latest.js"></script>
</head>
<body>
<header class="site-header" >
    <h1 class="site-title">Site Title</h1>
    <nav class="navbar">
        <ul class="menu">
            <li class="top-level-item">Menu item #1</li>
            <li class="top-level-item">Menu item #2</li>
            <li class="top-level-item">Menu item #3
                <ul class="submenu">
                    <li class="submenu-item">Menu item 3a</li>
                    <li class="submenu-item">Menu item 3b</li>
                    <li class="submenu-item">Menu item 3c</li>
                    <li class="submenu-item">menu item 3d</li>
                </ul>
            </li>
            <li class="top-level-item">M/enu item #4</li>
        </ul>
    </nav>
</header>
<div class="site-content">
    <main class="primary-content">
        <article>Article 1</article>
        <article>Article 2</article>
        <article>Article 3</article>
        <article>Article 4</article>
        <article>Article 5</article>
        <article>Article 6</article>
        <article>Article 7</article>
        <article>Article 8</article>
        <article>Article 9</article>
    </main>
    <div class="secondary-content">
        <aside class="widget">Widget 1</aside>
        <aside class="widget">Widget 2</aside>
        <aside class="widget">Widget 3</aside>
    </div><!-- .secondary-content -->
</div><!-- .site-content -->
<footer class="site-footer">
This is the footer.
</footer>
</body>
</html>
</code>
<code>
<html class="no-js">
<head>
	<title>FlexBox Test</title>
	<link href="flexbox.css" type="text/css" rel="stylesheet"/>	
	<script src="modernizr-latest.js"></script>
</head>
<body>
<header class="site-header" >
	<h1 class="site-title">Site Title</h1>
	<nav class="navbar">
		<ul class="menu">
			<li class="top-level-item">Menu item #1</li>
			<li class="top-level-item">Menu item #2</li>
			<li class="top-level-item">Menu item #3
				<ul class="submenu">
					<li class="submenu-item">Menu item 3a</li>
					<li class="submenu-item">Menu item 3b</li>
					<li class="submenu-item">Menu item 3c</li>
					<li class="submenu-item">menu item 3d</li>
				</ul>
			</li>
			<li class="top-level-item">M/enu item #4</li>
		</ul>
	</nav>
</header>
<div class="site-content">
	<main class="primary-content">
		<article>Article 1</article>
		<article>Article 2</article>
		<article>Article 3</article>
		<article>Article 4</article>
		<article>Article 5</article>
		<article>Article 6</article>
		<article>Article 7</article>
		<article>Article 8</article>
		<article>Article 9</article>
	</main>
	<div class="secondary-content">
		<aside class="widget">Widget 1</aside>
		<aside class="widget">Widget 2</aside>
		<aside class="widget">Widget 3</aside>
	</div><!-- .secondary-content -->
</div><!-- .site-content -->
<footer class="site-footer">
This is the footer.
</footer>
</body>
</html>
</code>

The CSS

Setup

First, some general layout properties that we will use for all versions of the spec. For simplicity of the example, I will assume support for the box-sizing property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code>
.site-content {      /* Div that will hold main content and sidebar */
    margin: 0;
    padding: 0;
}
.primary-content,    /* main content area */
.secondary-content,  /* sidebar */
.widget {
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    margin: 0;
    padding: 1rem;
    display: block;
}
 
</code>
<code>
.site-content {      /* Div that will hold main content and sidebar */
	margin: 0;
	padding: 0;
}
.primary-content,    /* main content area */
.secondary-content,  /* sidebar */
.widget {
	box-sizing: border-box;
	-moz-box-sizing: border-box;
	-webkit-box-sizing: border-box;
	margin: 0;
	padding: 1rem;
	display: block;
}

</code>

To demonstrate the flex-wrap property, we’ll implement the nav-bar as a row of items that will wrap around to the next line when the screen gets narrow. As a fallback, we’ll use inline-block.

Sidebar and Main Content

To place the sidebar and the main content next to each other, we only care about the flex model horizontal placement — support for flex-wrap is irrelevant.

Declaring flex elements

First, we declare the .site-content as a flex element, and that we want to arrange its children horizontally. Remember, .flexbox and .flexboxlegacy are the classes added by the Modernizr script to indicate flex model support.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<code>
.flexbox .site-content,
.flexboxlegacy .site-content {
 
   /* 2009 Syntax */
   display: box;
   display: -webkit-box;
   display: -moz-box;
   box-orient: horizontal;
   -webkit-box-orient: horizontal;
   -moz-box-orient: horizontal;
 
   /* 2011 Syntax (IE 10) */
   display: -ms-flexbox; 
   -ms-box-orient: horizontal;
   -ms-flex-direction: row;
 
   /* 2013 Syntax */
   display: flex; 
   display: -webkit-flex; 
   flex-direction: row; 
}
</code>
<code>
.flexbox .site-content,
.flexboxlegacy .site-content {

   /* 2009 Syntax */
   display: box;
   display: -webkit-box;
   display: -moz-box;
   box-orient: horizontal;
   -webkit-box-orient: horizontal;
   -moz-box-orient: horizontal;

   /* 2011 Syntax (IE 10) */
   display: -ms-flexbox; 
   -ms-box-orient: horizontal;
   -ms-flex-direction: row;

   /* 2013 Syntax */
   display: flex; 
   display: -webkit-flex; 
   flex-direction: row; 
}
</code>

The multiple “display” declarations aren’t a problem, because the browser will just ignore the ones that it doesn’t understand.

Sidebar fallback

When flex-box isn’t supported, we’ll use floats to lay out the sidebar and main content. If the Modernizr script can’t run (.no-js class still present), we’ll assume no flex-box support and use the fallback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code>
.no-flexbox.no-flexboxlegacy .site-content, 
.no-js .site-content {
   overflow: auto;
}
 
.no-flexbox.no-flexboxlegacy .primary-content,
.no-js .primary-content {
   float: left;
   width: 75%;
}
 
.no-flexbox.no-flexboxlegacy .secondary-content,
.no-js .secondary-content {
   float: right;
   width: 25%;
}
 
.site-footer {  
   clear: both;
}
</code>
<code>
.no-flexbox.no-flexboxlegacy .site-content, 
.no-js .site-content {
   overflow: auto;
}

.no-flexbox.no-flexboxlegacy .primary-content,
.no-js .primary-content {
   float: left;
   width: 75%;
}

.no-flexbox.no-flexboxlegacy .secondary-content,
.no-js .secondary-content {
   float: right;
   width: 25%;
}

.site-footer {  
   clear: both;
}
</code>

Flex-box: Setting widths of horizontally aligned elements

This is another area that may be counter-intuitive at first, if you don’t know what’s happening.

The flex-box model FIRST lays things out as it would without the flex-box model, THEN adjusts them per the flex-box settings. This means that if you set two columns to be equal width using flex 1 1 1 on each, they will probably be different widths. The pre-flex-box layout will give them widths based on the size of their content, and then flex-box would expand or contract them from that.

What’s more, setting the widths of the columns as percentages before applying flex-box doesn’t work.

Thus, for our content/sidebar 75%/25% split, we’ll set the (initial) widths to 3px and 1px respectively, and let flex-box expand them to fill the available space.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<code>
.flexbox .primary-content,
.flexboxlegacy .primary-content {
   width: 3px;
   /* 2011 Syntax */
   -ms-flex: 3 3;
 
   /* 2013 syntax */
   flex-grow: 3;
   -webkit-flex-grow: 3;
}
 
.flexbox .secondary-content,
.flexboxlegacy .secondary-content {
   width: 1px;
 
   /* 2011 Syntax */
   -ms-flex: 1 1;
 
   /* 2013 syntax */
   flex-grow: 1;
   -webkit-flex-grow: 1;
}
</code>
<code>
.flexbox .primary-content,
.flexboxlegacy .primary-content {
   width: 3px;
   /* 2011 Syntax */
   -ms-flex: 3 3;

   /* 2013 syntax */
   flex-grow: 3;
   -webkit-flex-grow: 3;
}

.flexbox .secondary-content,
.flexboxlegacy .secondary-content {
   width: 1px;

   /* 2011 Syntax */
   -ms-flex: 1 1;

   /* 2013 syntax */
   flex-grow: 1;
   -webkit-flex-grow: 1;
}
</code>

 

Note that the flex shorthand property exists in both the 2011 IE 10 syntax and in the 2013 syntax, but with different meanings for the arguments.

Flex-wrap for responsive element placement

Now, for our navbar. The top-level nav-bar items will lay out side-by-side, and wrap to the next line if the viewport is too narrow to show them all. Hovering over an item will cause a submenu to appear. Each item on the menu bar will wrap to the next row when the viewport is too narrow to contain them all. Hovering over an item will cause a sub-menu to appear when there is one. (For the sake of this example, we’ll ignore touch-screens. This is not production-ready!)

Since Modernizr checks specifically for flex-wrap, it’s exactly what we want here. Browsers that don’t support flex-wrap will get inline-block as a fallback.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<code>
.flexbox .menu
{ 
   /* === 2011 Syntax === */
 
   /* declare the use of flex-box */
   display: -ms-flexbox; 
 
   /* Arrange the elements horizontally */
   -ms-box-orient: horizontal;
   -ms-flex-direction: row;
 
   /* Where to place the first element */
   -ms-flex-pack: start;
 
   /* Wrap elements to the next line when the screen gets narrow */
   -ms-flex-wrap: wrap;
 
   /* === 2013 Syntax === */
 
   /* declare the use of flex-box */
   display: flex; 
   display: -webkit-flex; 
   display: -moz-flex; 
   display: -ms-flex; 
 
   /* Arrange the elements horizontally */
   flex-direction: row; 
 
   /* Where to place the first element */
   justify-content: flex-start;
 
   /* Wrap elements to the next line when the screen gets narrow */
   flex-wrap: wrap;
   -webkit-flex-wrap: wrap;
}
 
</code>
<code>
.flexbox .menu
{ 
   /* === 2011 Syntax === */

   /* declare the use of flex-box */
   display: -ms-flexbox; 

   /* Arrange the elements horizontally */
   -ms-box-orient: horizontal;
   -ms-flex-direction: row;

   /* Where to place the first element */
   -ms-flex-pack: start;

   /* Wrap elements to the next line when the screen gets narrow */
   -ms-flex-wrap: wrap;

   /* === 2013 Syntax === */

   /* declare the use of flex-box */
   display: flex; 
   display: -webkit-flex; 
   display: -moz-flex; 
   display: -ms-flex; 

   /* Arrange the elements horizontally */
   flex-direction: row; 

   /* Where to place the first element */
   justify-content: flex-start;

   /* Wrap elements to the next line when the screen gets narrow */
   flex-wrap: wrap;
   -webkit-flex-wrap: wrap;
}

</code>

Our fallback:

1
2
3
4
5
6
7
8
<code>
.no-flexbox nav .top-level-item,
.no-js nav .top-level-item
{
   display: inline-block;
}
 
</code>
<code>
.no-flexbox nav .top-level-item,
.no-js nav .top-level-item
{
   display: inline-block;
}

</code>

Initially hidden sub-menus

The CSS for these sub-menus is the same regardless of whether we used flex-box or inline-block. We want the menus to initially be hidden, but become visible when someone hovers over an item:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<code>
 
.submenu {
   position: absolute;
   margin: 0;
   left: 0;
   top: 100%;
   display: none;
   background-color: #a9d;
}
 
.top-level-item:hover .submenu,
.submenu-item:hover .submenu
{
   display: block;
}
 
</code>
<code>

.submenu {
   position: absolute;
   margin: 0;
   left: 0;
   top: 100%;
   display: none;
   background-color: #a9d;
}

.top-level-item:hover .submenu,
.submenu-item:hover .submenu
{
   display: block;
}

</code>

Additional nav-bar properties

These are the properties that apply to both the top-level navbar items and the sub-menu items, (regardless of whether we used flex-box for the navbar layout )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<code>
 
/* When someone hovers over a menu item, use a CSS tranition to change it's 
   background color.*/
nav li:hover {
   background-color: #993;
   transition: background-color 1s ease 0s;
   -webkit-transition: background-color 1s ease 0s;
   -moz-transition: background-color 1s ease 0s;
   -o-transition: background-color 1s ease 0s;
}
 
.top-level-item,
.submenu-item
{
   min-width: 8rem;
 
   /* In order to make the sub-menus appear next to their parent items using   display:absolute,
      we need to change the display of the parent item to 'relative'.*/
   position: relative;
 
   /* When someone un-hovers from a menu item, use a css transition to change the
      background color back to what it was */
   background-color: #dce;
   transition: background-color 1s ease 0s;
   -webkit-transition: background-color 1s ease 0s;
   -moz-transition: background-color 1s ease 0s;
   -o-transition: background-color 1s ease 0s;
 
   /* Give the menu items some breathing room */
   padding: .5rem;
   margin: .5rem;
}
 
</code>
<code>

/* When someone hovers over a menu item, use a CSS tranition to change it's 
   background color.*/
nav li:hover {
   background-color: #993;
   transition: background-color 1s ease 0s;
   -webkit-transition: background-color 1s ease 0s;
   -moz-transition: background-color 1s ease 0s;
   -o-transition: background-color 1s ease 0s;
}

.top-level-item,
.submenu-item
{
   min-width: 8rem;

   /* In order to make the sub-menus appear next to their parent items using   display:absolute,
      we need to change the display of the parent item to 'relative'.*/
   position: relative;

   /* When someone un-hovers from a menu item, use a css transition to change the
      background color back to what it was */
   background-color: #dce;
   transition: background-color 1s ease 0s;
   -webkit-transition: background-color 1s ease 0s;
   -moz-transition: background-color 1s ease 0s;
   -o-transition: background-color 1s ease 0s;

   /* Give the menu items some breathing room */
   padding: .5rem;
   margin: .5rem;
}

</code>

Full working example

This page demonstrates our example.The CSS for the page adds some additional margins, padding, background-colors, and outlines to our example to make it visually clear which element is where.

Happy styling!

Further Resources

Start a conversation!

This post looks bare without your input.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">