Monday, February 22, 2016

A Modern Menu

I've been exploring a little, stretching my coding muscles again to see just how easy it would be to write an accessible navigation menu that slides out from the left on mobile but lies horizontally on a wide(r) screen. Now, let me begin by saying that writing one is easy...but writing a progressive, fully semantic version is not necessarily so. This post, however, will explain just that - how to write a progressively enhanced, fully semantic version that is accessible.

Rather than bore everyone with tedious code listings (yes, I'll have a few), I'm going to link to the proof-of-concept that will also have fully commented code, and here I'll give a general overview as well as a few things you'll want to notice along the way.

If this is your first foray into accessible coding, do yourself a favor and get the WAVE Evaluation Tool extension for Google Chrome, and if you're not already using the Mobile/Responsive Web Design Tester extension, get that as well.

As always, we first start with the markup and do the things we would normally do - add the lang attribute to the HTML tag, add a descriptive TITLE to the HEAD, and add a descriptive H1 to the BODY. Here's where I start my navigational menu code...inside the H1. In addition to using the H1 as a banner, I'm adding a 'button' that will toggle the navigation menu (note that this is only for mobile devices). Since this is only a visual interface for the menu, I'm not adding anything to it - no ARIA role labels. The 'button' span is empty, so without ARIA labels or role, accessibility technology will ignore it (as it should). After the H1, I'll add the actual menu...but more on that in a couple of paragraphs.

The user interface for mobile users, when the navigation menu is closed, will show a button that will display a hamburger symbol and when it's open it will display the x typically used on close buttons.

Note that I'm not actually using a BUTTON tag but a SPAN tag that acts like a button...and the primary reason for this is because the icon displayed inside the button is controlled using CSS and that's really impossible to do when you use a BUTTON tag. By attaching an event listener to the click event on the button SPAN, I add (or remove) opened to (or from) the H1 tag. The opened H1 and an adjacent sibling selector for the menu will control whether the menu is seen or not. If you want to use a different device to toggle the menu, feel free - and simply change the CSS so that the 'opened' class is attached to the menu. Since the content is not in the markup, I don't need any ARIA attributes like aria-hidden even though it's a control for the visual interface...and because the button in the H1 is only used on mobile, I wrap the styling in a media query using a mobile breakpoint.


HTML
<body>   <h1>     <span class="banner">Responsive Navigation Menu</span>     <span class="mobile-only button open close"></span>   </h1>   <div class="menu-overlay">


CSS
@media screen and (max-width:480px) {   h1 > .banner {     float:left;     height:1.65em;     line-height:1.65em;     width:80%;   }   h1 > .button {     border-radius:0.1em;     border:0.01em solid #ccc;     cursor:pointer;     display:inline-block;     font-size:1.25em;     font-weight:800;     height:1.1em;     padding:0.1em;     position:static;     width:1.1em;   }   h1 > .button.open.close::after {     content:"\2261";     cursor:pointer;     display:inline-block;     font-size:1.5em;     line-height:0.75em;     text-align:center;     width:0.75em;   }   h1.opened > .button.open.close::after {     content:"\00D7";   }

Now that we have the H1 set, I'm going to add the navigation menu to the page. I'm choosing to add the navigation menu before the content because (1) it's easier to style that way and (2) when the links are at the top it's easier for someone to jump to another location before all the other content is read - and they don't have to take extra steps, like using their AT to step through a bunch of landmarks.

HTML
  </h1>   <div class="menu-overlay">     <ul id="main-menu" role="navigation">       <li class="item">         <a href="?menu-item-1">Menu Item 1</a>         <span class="indicator"></span>       </li>       <li>         <a href="?menu-item-2">           Menu Item 2         </a>         <ul class="submenu">           <li class="item">             <a href="?menu-item-2.menu-item-A">               Menu Item A             </a>           </li>           <li class="item">             </a href="?menu-item-2.menu-item-B">               Menu Item B             </a>           </li>         </ul>         <span class="indicator"></span>       </li>     </ul>   </div>

There are a few things of note in the code. First, I've added a ARIA role (navigation) to the list. Rather than go into detail here about what a navigation role means, I'll instead point you to the W3 page about ARIA roles. Second, this example uses a sub-menu, which will become important when we talk about the CSS and the JavaScript. Additionally, I've identified LI items that contain menu items as items, which enables me to add LI tags that are used as separators. Each clickable item is wrapped in an anchor (A) tag, which allows me to leave sub-menu headers in tags that aren't automatically identified as clickable, e.g., SPAN. Finally, there is a SPAN tag with the class indicator regardless of whether or not there is a sub-menu. This enables a sub-menu visual indicator, which is not needed by accessibility technology, which will read the submenu because it's hidden using clip rather than a technique that would render the sub-menu invisible to AT.

CSS
ul[role="navigation"] > .item > .submenu {   background-color:#fff;   border-top:none;   border:1px solid #ccc;   box-sizing:border-box;   clip:rect(0, 0, 0, 0);   color:#333;   left:0;   list-style-type:none;   margin:0 -1px;   padding:0.5em;   position:absolute;   right:0;   transition:clip 1s;   z-index:3; }


There are a few things of note about the CSS. First, the style rules for the sub-menu will drop the sub-menu below the primary menu and make it appear as wide as the primary menu. Although this approach is not ideal, we're going to fix it using JavaScript - which will hit most users and those who don't have JavaScript running (or those who have a page with borked JavaScript) will still have access to the navigation menu. If you're coding for a mobile device and are using a toggle button, you might want to consider how users will get to the menu if their JavaScript is borked - you might even consider using a focus or hover pseudoclass.

Second, the initial value of the margin-left and margin-right is the border-width on the primary menu (ul[role="navigation"]) multiplied by -1. This extends the left and right border to the full width of the primary menu.

Third, by setting the position to absolute, we can change the left and right values and the menu will become more like a drop-down menu (remember that I said we'd fix the width of the menu with JavaScript just two paragraphs ago - well here it is, the initMenu function does it).

JavaScript
function initMenu(menu) {   var contained,       count,       index,       items,       mnu_item;   items = menu.getElementsByTagName('li');   count = items.length - 1;   while (count > -1) {     mnu_item = items.item(count);     contained = mnu_item.childNodes;     index = contained.length - 1;     while (index > -1) {       if ((/\bsubmenu\b/).test(contained.item(index).className)) {         contained.item(index).style.left = mnu_item.offsetLeft + 'px';         contained.item(index).style.margin = '0';         contained.item(index).style.right = 'auto';         break;       }       index -= 1;     }     count -= 1;   } }

Now, simply add a call to your JavaScript function to initialize the menu - something like initMenu(document.getElementById('main-menu')); - and you're done. The initMenu function will set the left position of the sub-menu UL to the left position of its LI container, will set the right value to auto (which shortens the width) and adjusts the margin so that everything lines up neatly.

So that's it. One last note - although I generally use a 'mobile first' approach (making the style sheet for mobile and then writing a media query for everything else) I didn't do it that way this time because the styles are different enough that they're difficult to re-use. In the proof of concept I've linked both stylesheets, but in your production code you might want to pursue an adaptive approach so only the mobile stylesheet is downloaded for mobile devices to minimize the payload - on the other hand, the stylesheets are pretty light as they are so it might not matter. Oh...and this is the proof of concept.

Happy coding.



No comments:

Post a Comment