Progressive enhancement menus with Modernizr

Make your pop-up menus more interesting and fun for users of capable browsers through progressive enhancement. Creative design and web development consultant Faruk Ates explains how

While support for most CSS3 techniques is currently limited to WebKit-based browsers, the day will come when they’re as widely implemented as CSS2.1 is today. To that end, my work philosophy is (to paraphrase Wayne Gretzky) that I design to where the web is going to be, not where it has been. With that in mind, let’s go through the process of using progressive enhancement to make a simple CSS pop-out menu progressively more interesting and fun the more capable the reader’s browser is.

While support for most CSS3 techniques is currently limited to WebKit-based browsers, the day will come when they’re as widely implemented as CSS2.1 is today. To that end, my work philosophy is (to paraphrase Wayne Gretzky) that I design to where the web is going to be, not where it has been. With that in mind, let’s go through the process of using progressive enhancement to make a simple CSS pop-out menu progressively more interesting and fun the more capable the reader’s browser is.

To ensure fine control over the experience at every level we’ll use Modernizr, a small, free JavaScript toolkit that I created with Paul Irish. Modernizr detects a browser’s capabilities and makes that info available to you, giving you the freedom of if/else conditions in your CSS and your JavaScript.

First we have to write our markup. I’ve created a little site called My Favorite Sci-Fi Things and made some fancy sci-fi art to go with it. The menu consists of four parent items: Movies, Books, TV Shows and Games. Each parent then contains my personal top five of Sci-Fi ‘things’ in that category. The submenus are all hidden by default and will be triggered by a mouseover state, done entirely in CSS via the :hover property.

Note: to be properly accessible, we should use JavaScript to also support keyboard focus (which can’t automatically be given to non-hyperlink elements, such as we’re doing). Techniques for making this menu better and more accessible with JavaScript are, alas, well beyond the scope of this tutorial.

Progressive enhancement menus with Modernizr: finished project

Our first screenshot shows the site fully designed and the submenus hidden. Hovering over the space of the submenus doesn’t do anything – as planned

Getting started

Before we write a single line of CSS, we should have a first draft of our structure (HTML) complete. Viewing it in the browser should yield a clean but richly semantic (and accessible) page.

I’ll skip over the basic styling parts to the fully designed page with a fancy header graphic, some planet art as the content of the page, and our menu – with the submenus hidden.
Let’s dive into the code now. You can follow along with the files above or at First we’ll look at how the submenus are hidden; since each is a ‘top five’, I’m using an <ol> (ordered list) element for them:

nav li.parent ol { left: 0; opacity: 0; margin: 0 0 0 -40px; padding: 0 30px 30px; position: absolute; width: 100px; } nav li.parent:hover ol { opacity: 1; z-index: 10; }

Pretty standard fare styling, with an opacity of 0 to hide things. The negative left margin centres our menu to each 80 pixels-wide parent menu item, and the padding provides some leeway for the cursor to go out of bounds when we hover over the submenu.

Without that padding our menu would feel jittery, forcing visitors to stay on the exact (visible) pixels of the submenu to avoid it disappearing. That wouldn’t be a great user experience, so we add some padding.

Progressive enhancement menus with Modernizr: with CSS animations

The CSS Animations are kicking in, making our submenu items slide in from two different directions for a cool and fun effect

The :hover state on the parent <li> element is where we make our submenu visible by changing its opacity to 1. You may wonder about that z-index:10. A more thorough search through our CSS will reveal that I’ve made the main content article z-index:1 and position:relative, which prevents the invisible menu from effectively sitting atop of our content and interfering with any mouseovers we may have in that area.

However, because we want to smoothly fade the menus in and out later on – something that doesn’t work so well when you also toggle its z-index from 0 to 1 – we have to create some buffer for the z-index to animate.

A value of 10 works in our case, but your mileage may vary. Another thing is that the z-index will work in browsers where the opacity property doesn’t work (read: older versions of Internet Explorer), meaning we have a good fallback in place already.
Now let’s add some CSS Transitions to make the submenus actually fade in and out. As you can see, we’ll transition not just the opacity property but also the z-index. That way, when our submenu fades out it won’t immediately fall behind the content again and fade out where we don’t see it!

Progressive enhancement menus with Modernizr: transition opacity

This screenshot better illustrates that the opacity Transition is at work at the same time, even though we didn’t specify that in our Animation

We’re adding the transition: property, complete with vendor-prefixed versions for each browser, to the original nav li.parent ol CSS rule.

nav li.parent ol { /* existing CSS properties hidden for brevity; */ -webkit-transition: opacity .3s ease-out, z-index .2s linear; -moz-transition: opacity .3s ease-out, z-index .2s linear; -o-transition: opacity .3s ease-out, z-index .2s linear; transition: opacity .3s ease-out, z-index .2s linear; }

Note: We’re using the same transition to fade our submenu in and out in this case; if you want a quick fade-in but a slow fade-out, your fade-in transition must be written into the :hover state rule, and your fade-out transition has to be added to the regular state CSS rule.

Most people initially write the fade-in on the regular state and the fade-out into the :hover rule, producing the opposite effect of what they expect. This may seem unintuitive, so remember this: transitions may work over time, but state changes are immediate.

By now, we have a functioning menu with submenus that show up when you hover over their parent menu item. In older IE browsers, only the z-index works. In newer browsers it will use opacity, and in all the modern browsers it will transition that opacity property for a nice fading effect. Already our menu is quite lovely, but we’re here today to really push the envelope, right? Right.

Before diving into more code, we need to get a good understanding of the upcoming technique: CSS Animations. After all, there are subtle but very important differences between CSS Transitions and CSS Animations, and since we’re about to use both it’s important to understand them.

Progressive enhancement menus with Modernizr: completed animation

Now our animation is complete, and we have the same result as browsers without support for CSS Animations. The effect is just nicer with support

CSS Transitions transition properties when the state of a property changes. This is often triggered by a state change on the element or one of its parents; think of a :hover or :focus action, or a class or id attribute change somewhere up the DOM tree. When a property is set to transition – as we’ve done by telling the browser to transition both opacity and z-index – it takes your parameters and automatically transitions from state A (old) to state B (new). An important takeaway here: Transitions only occur between states A and B, and that’s the extent of what they can do. You can fake multiple states using JavaScript and multiple CSS classes, but CSS Animations are a better solution.


Unlike Transitions, CSS Animations enable you to specify multiple keyframes, which you can think of as states but shouldn’t, as they’re separate from the states we usually mean when discussing browsers and CSS. Rather than being a transition between two state changes, CSS Animations live entirely on their own accord: you specify a set of keyframes (a minimum of two, start and finish) and give the animation a name. Then you tell an element to animate by assigning it that same name. Voil: animations in your page, done in CSS.

The animations in CSS Animations don’t require a trigger: they occur the moment you assign the animation name. That said, it’s entirely possible to assign that animation name upon a state change such as :hover. When you do that, the animation will only run once you hover over the element, and not simply as soon as the page loads.

To sum up: CSS Transitions are only a way to tell the browser to transition one or more properties when their state changes, while CSS Animations enable you to specify keyframes and a name to form a complete (and independent) animation, and you make that animation run simply by assigning the animation name to an element in a CSS rule. Whether you do the latter by a state change or not is up to you.

You may wonder how Animations and Transitions interact or interfere with each other. The answer is relatively simple: Animations support the same properties as Transitions, which means that you can have a Transition set for a certain property (left: for example) which coincides with (part of) an animation you’re also executing on the same element. When this is the case, the Animation takes priority and overrides the Transition, but only for as long as the Animation is set to run.

The instant your Animation is complete, the browser continues with whatever Transition is meant to be taking place, regardless of what the Animation might have been doing. That makes combining Transitions and Animations a tricky business that can result in very jarring, jumpy behaviours if you don’t know exactly what’s going to happen.

I’ll illustrate this with an example. Say you have an absolutely positioned element and you’ve given it a transition for the left: and top: properties...

#parent_box #my_elem { /* Some other properties */ left: 100px; position: absolute; top: 100px; transition: left 1s ease-out, top 1s ease-out; }

… and you’ve created a hover state where the box is positioned elsewhere in its parent box:

#parent_box:hover #my_elem { left: 200px; top: 200px; }

Hovering over the parent container would create a one-second transition for our element, moving it diagonally to the south-east (see below). But what happens if we add this half-second animation to the mix?

@keyframes slide { 0% { left: 100px; } 100% { left: 0; } } #parent_box:hover #my_elem { left: 200px; top: 200px; animation: slide .5s ease-out; }

The animation only animates the left: property. The Transition we specified for top: will occur in addition to the Animation, but the Animation will override the Transition for left: – that is, until the Animation is complete, after which the element will suddenly jump to the position it would have been if our Animation had never existed.

Progressive enhancement menus with Modernizr: transition

Hovering over the parent container creates a one-second transition for our element, moving it diagonally to the south-east

The screengrab below explains the behaviour visually in three panels, in which you can see that the box will jump the very first fraction of a second after the Animation is complete. This behaviour is straightforward and expectable, so it shouldn’t come as a surprise to you. That said, it is important to keep this in mind, in case you experience strange, jumpy behaviour when dealing with Transitions and Animations.

Why all this detailed information explaining a conflict-situation between Animations and Transitions? Because we’re going to take advantage of this behaviour. We’re going to make critical use of the fact that Animations override Transitions, and create a cool :hover transition that makes our submenu items go from A to B to C, where B is the on-hover state. This effect cannot be done with Transitions alone (at least, not without also using tricky JavaScript and multiple classes).

On the slide

Returning to our sci-fi site and menu shenanigans, I now want the submenu items to slide into place from one direction. Once you mouse out again, I want them to slide out, continuing in the direction they were heading. And for added seasoning, I want them to alternate directions. This will really bring these submenus to life – in the browsers that can, anyway.

Let’s start by defining the two animations we’ll need. As mentioned earlier, for CSS Animations you write out keyframes. You do this with the @keyframes at-rule, followed by an identifier (an alphanumerical string that begins with a letter), followed by the actual keyframes blocks, which must include 0% and 100%. The words “from” and “to” also work, but all other keyframes in between must be specified as percentages. We’ll call our animations slideLTR (for left-to-right) and slideRTL and specify their keyframes accordingly:

@keyframes slideLTR { 0% { left: -50px; } 100% { left: 0; } } @keyframes slideRTL { 0% { left: 50px; } 100% { left: 0; } }

Progressive enhancement menus with Modernizr: half-second CSS animation

Adding a half-second CSS Animation to our Transition makes the box jump once the animation is completed

I’m showing only the standards-compliant version, but unfortunately – and this is something I think is truly a shame – you’ll need to write out the @keyframes with vendor-prefixes as well, like this:

@-webkit-keyframes slideLTR { 0% { left: -50px; } 100% { left: 0; } } @-webkit-keyframes slideRTL { 0% { left: 50px; } 100% { left: 0; } }

This means that if you want to be properly forward-compatible, you’ll also have to specify your animations with @-moz-keyframes and @-o-keyframes, just like with other vendor-prefixed CSS.

The need for Modernizr

Now let’s see why we need Modernizr. As mentioned before, Modernizr detects the capabilities of the browser for CSS3 and HTML5 features (and related technologies), and makes the results of those tests available to us. One way it does this is by adding classes to the HTML element that indicate whether or not a certain feature is supported.

In the case of CSS Animations, this means that the HTML element will have the class cssanimations (and many others) if the browser supports the feature, and the class no-cssanimations if not. Based on that, we can serve certain CSS rules and properties only to the browsers that actually support animations – notice, too, the better practice here of doing feature detection instead of sniffing for the UA, which you should never do.

Our submenu items need some different properties when the browser supports CSS Animations, so let’s get to it:

.cssanimations nav ol li a { left: 50px; opacity: 0; position: relative; -webkit-transition: left .2s ease-out, opacity .2s ease-out, background-color .2s ease-out; -moz-transition: left .2s ease-out, opacity .2s ease-out, background-color .2s ease-out; -o-transition: left .2s ease-out, opacity .2s ease-out, background-color .2s ease-out; transition: left .2s ease-out, opacity .2s ease-out, background-color .2s ease-out; }

The .cssanimations serves as a conditional, making only browsers that understand animations include these rules. Rules which, as you may well imagine, we wouldn’t want to be applied for browsers that can’t do what we’re about to do, because we’re positioning the items well to the right. As added seasoning we’ll make the even items in our list sit far to the left instead:

.cssanimations nav ol li:nth-child(even) a { left: -50px; }

You may have noticed that I haven’t introduced any animations yet. That’s because we don’t want the animation to happen on the regular state, which would make it run as soon as the page loads. No, we want it to trigger once the user hovers over the parent menu item. The transitions you see above are to handle the mouse-out stage, ie when the mouse leaves the submenu.
To finish it, we’ll add the :hover states and the animations:

.cssanimations nav li.parent:hover ol li a { left: 0; opacity: 1; -webkit-animation: slideLTR .3s ease-out; -moz-animation: slideLTR .3s ease-out; -o-animation: slideLTR .3s ease-out; animation: slideLTR .3s ease-out; } .cssanimations nav li.parent:hover ol li:nth-child(even) a { -webkit-animation: slideRTL .3s ease-out; -moz-animation: slideRTL .3s ease-out; -o-animation: slideRTL .3s ease-out; animation: slideRTL .3s ease-out; }

And there we have it! Now our menu items fade and slide in, alternating from left or right, and once the user mouses away, the items continue their respective direction whilst fading out again. Check it out in a WebKit browser for the full effect and marvel at our creation.

One useful rule of thumb to employ when combining Transitions and Animations: make animations longer than transitions, because you don’t want glitches when the animation is done and the browser rounds up to still render part of the transition you overruled.

Some closing notes: all this could be done via JavaScript and made to work in more browsers, but it’s compelling just how much you can accomplish with some quick CSS rules alone.
Excluding the vendor-prefix redundancies, it’s only taken about 20 lines of progressive enhancement CSS to turn a nicely transitioning menu into a really slick one for browsers that understand the CSS. Not too shabby.