Grid of Widgets With Flexbox

Lately I’ve been dipping my toes into the Flexbox pool. So far, the water is fine.

I set out to solve a problem that happens when floated widgets in a grid layout are not all the same height, and they get hung up as they reflow.

What should look like this:
a 3x3 grid of floated widgets

…ends up at certain screen widths looking like this:
the layout fails at certain screen widths.

Flexbox solves this problem by stretching each widget in a row to the height of the tallest widget in that row. The results look like this:
all widgets in each row match the height of the tallest in the row

I made a flexbox widget tutorial, showing how I used the flexbox layout model to create multiple rows of side-by-side widgets.

Keyboard Accessible Drop-Down Menu

Drop-down menus can be a great way to save screen real estate, but the standard drop-down has one major problem: it’s not accessible to people who use the keyboard to navigate.

Here’s how to fix that with jQuery.

First, the basic menu HTML:

<nav id="nav-main">
  <ul>
    <li><a href="javascript:;">Rubber Boots</a></li>
    <li><a href="javascript:;">Unicycle Parts</a></li>
    <li><a href="javascript:;">Dog Costumes</a>
      <ul>
        <li><a href="javascript:;">Pirate</a></li>
        <li><a href="javascript:;">Clown</a></li>
        <li><a href="javascript:;">Shark</a></li>
      </ul>
    </li>
    <li><a href="javascript:;">Vintage Tubas</a></li>
    <li><a href="javascript:;">Magical Items</a>
      <ul>
        <li><a href="javascript:;">Cloaks</a></li>
        <li><a href="javascript:;">Wands</a></li>
        <li><a href="javascript:;">Rings</a></li>
        <li><a href="javascript:;">Glowing Orbs</a></li>
      </ul>
    </li>
    <li><a href="javascript:;">Ostrich Feathers</a></li>
  </ul>
</nav><!-- /#nav-main -->

Next, some basic CSS:

/* NAVIGATION */

/* reset global list styles */
nav ul,
nav li {
	position: static;
	left: 0;
	list-style: none;
	padding: 0;
	margin: 0;	
}

/* style main navigation */
#nav-main {
	float: right;
	background-color: #EEE;
}

#nav-main li {
	float: left;
	position: relative;	/* set positioning context for nested lists */
}

#nav-main li li {
	width: 100%;
	border-top: 1px solid;
}

#nav-main li ul {
	position: absolute;
	top: 2.25em;		/* nested list must overlap parent anchor */
	margin-left: 1.5em;
	z-index: 1000;

	width: 8em;		/* adjust as needed for width of text */	
	background-color: #EEE;
	border: 1px solid;
	border-top: none;
}

#nav-main a:link,
#nav-main a:visited {
	display: block;
	text-decoration: none;
	color: #000;
	padding: .75em;		
}

#nav-main a:hover,
#nav-main a:focus {
		background-color: #000;
		color: #FFF;
}

The nested lists are absolutely positioned, relative to their parent list items. Now we move the nested lists offscreen with a left offset.

  • A negative left offset value will send the nested lists to the left.
  • A positive left offset value would send the nested lists to the right. We don’t want to do this, as it creates a wide page with horizontal scrolling.
#nav-main li ul {
	blah blah blah
	left: -999em;	/* negative value sets nested list off the left side of the screen */
}

We bring the nested lists back into place by changing their left offset to auto when the parent list item is hovered or has focus:

#nav-main li:hover ul,
#nav-main li:focus ul {
	left: auto;
}

The Problem

However, this presents an accessibility problem. The browser knows when a list items is hovered, but list items are not natively focusable.

As this example shows, tabbing through the links by keyboard does not offer a good user experience.

The Solution

A little jQuery provides an elegant solution by adding a class name to the nested list when the parent li’s anchor has focus.

Add the script to your HTML:

<!-- load jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<!-- add class="focused" to submenu list when parent anchor has focus -->
<script>
// parent
$(function() {
  $('li a').focus( function () {
    $(this).siblings('li ul').addClass('focused');
  }).blur(function(){
    $(this).siblings('li ul').removeClass('focused');
  });
// submenu
  $('li a').focus( function () {
    $(this).parents('li ul').addClass('focused');
  }).blur(function(){
    $(this).parents('li ul').removeClass('focused');
  });
});
</script>

… and then change your li:focus selector from this:

#nav-main li:hover ul,
#nav-main li:focus ul {
	left: auto;
}

… to this:

#nav-main li:hover ul,
#nav-main li ul.focused {
	left: auto;
}

Now we have keyboard accessible drop-down navigation. Tab through the links to see it work.