Full-width justified vertically centered navbar

In a couple of recent projects I’ve faced designs where the main navigation bar gave me more trouble than usual. These are the prerequisites that make the problem tricky to solve:

  • The navbar is full-width and the links have to fill up the entire width regardless of how many of them there are
  • The text in each link needs to be vertically centered and wrap to multiple lines if necessary
  • The entire area of each item in the navbar needs to be clickable

After exploring lots of different methods I’ve found two solutions to the problem. As is often the case, both have their drawbacks and you need to choose which compromise to make.

Examples of both solutions can be found on the Full-width justified vertically centered navbar demo page.

Solution 1: standard list + links markup pattern

The first solution uses this very common markup pattern for groups of navigational links:

<div class="nav-main" role="navigation">
    <ul>
        <li><a href="/">Section</a></li>
        <li><a href="/">Section</a></li>
        <li><a href="/">Section</a></li>
    </ul>
</div>

Making that display as intended is not a big deal actually, if you can disregard IE7. Just use display:table and display:table-cell. Here’s the CSS you need:

.nav-main ul {
    display:table;
    border-collapse:collapse;
    width:100%;
    margin:0 0 20px;
    padding:0;
    list-style:none;
}
.nav-main li {
    display:table-cell;
    width:1%;
    border:1px solid #ddd;
    background:#eee;
    vertical-align:middle;
    text-align:center;
}
.nav-main a {
    display:block;
    padding:10px 4px;
    color:#000;
    text-decoration:none;
}

That CSS gets us the look we want. But the problem is that only the a element is clickable, and it doesn’t extend to the entire height of the li element. If some of the links line wrap but not others, the entire area is clickable only in the navigation items that have wrapped text (and in case some links wrap onto three or more lines, only those that have the highest number of text line). If you don’t get any line wrapped links in the demo, just make your browser window narrower to see what I mean.

You could avoid line-wrapping by adding a white-space:nowrap declaration, but in this case one of the requirements is that links can wrap if necessary. The result is that if you add some styling to the links when they are hovered over or receive keyboard focus, it will be obvious that single-line links don’t cover the entire area. And when you click one of the items, nothing happens unless you click on the actual link.

Creating a hover effect is easy: .nav-main li:hover { }. But that does nothing for keyboard users, and it does not make the li elements clickable.

If there is a pure CSS solution to this (using the same markup) I’d be happy to hear it, but I have not been able to find one or figure one out. So I added a little bit of JavaScript (jQuery-based) to fix it:

$('.nav-main').each(function () {
    var nav = $(this);
    // Give the containing element a class name to let the CSS know it is active
    nav.addClass('active');
    nav.find('li').each(function () {
        var item = $(this);
        var link = item.find('a');
        var href = link.attr('href');
        // When an li element is clicked, open the href of the link it contains
        item.click(function (e) {
            if (!$(e.target).is(link)) {
                // Check if a key that indicates that the user wants the link to open in a new tab or window is pressed. See https://github.com/Lingonmirakel/smartclick.
                if (e.ctrlKey || e.metaKey || e.which === 2) {
                    window.open(href, '');
                } else {
                    window.location = href;
                }
            }
        });
        // Set a class on the li when the link gets focus to enable visual feedback for keyboard navigation
        link.focus(function () {
            item.addClass('focus');
        }).blur(function () {
            item.removeClass('focus');
        });
    });
});

The script does a few things:

  • Gives the .nav-main element an “active” class name that is used to set cursor:pointer on the li elements to indicate that theuy are clickable
  • Makes clicks on an li element open the href of its contained link
  • Toggles a ”focus” class name on the li elements when their contained link receives or loses keyboard focus

With that in place you can style the hover and focus states like this (note the use of .focus instead of :focus):

.nav-main a:hover,
.nav-main a:focus {
    outline:none;
    color:#fff;
    background:#000;
}
.nav-main.active li:hover {
    cursor:pointer;
}
.nav-main.active li:hover,
.nav-main.active li.focus {
    background:#000;
}
.nav-main.active li:hover a {
    color:#fff;
}

You may wonder about outline:none in that first rule. I’ve removed it because otherwise it would reveal that the link doesn’t cover the whole list item, and in this example it’s pretty obvious visually which link has focus even without the focus ring.

If JavaScript is off, the “full-height” clickability illusion will be ruined, but the links will still be there and fully functional.

Solution 2: Links not in a list

The problem can actually be solved without JavaScript if you do not put the links in a list, like this:

<div class="nav-main" role="navigation">
    <a href="/">Home</a>
    <a href="/">Section two</a>
    <a href="/">Section three</a>
    <a href="/">Another section</a>
    <a href="/">A section with a longer name</a>
    <a href="/">Section six</a>
    <a href="/">This is section seven</a>
    <a href="/">Section 8</a>
</div>

Here’s the corresponding CSS:

.nav-main {
    display:table;
    border-collapse:collapse;
    width:100%;
    margin:0 0 20px;
}
.nav-main a {
    display:table-cell;
    width:1%;
    padding:10px;
    border:1px solid #ddd;
    outline:none;
    background:#eee;
    color:#000;
    text-decoration:none;
    vertical-align:middle;
    text-align:center;
}
.nav-main a:hover,
.nav-main a:focus {
    background:#000;
    color:#fff;
}

Done. Much simpler than having to involve JavaScript. But see the caveat in the next section before picking this solution.

To list or not to list

The code for the second solution is a lot simpler, but it does require deviating from the standard markup pattern for navigation (for a much longer discussion about using lists or not for navigation, see Wrapup of Navigation in Lists).

This deviation isn’t necessarily a huge problem, but my personal preference is to stick with the list pattern. Looking at specifications, the HTML5 spec for the nav element says this:

In cases where the content of a nav element represents a list of items, use list markup to aid understanding and navigation.

I haven’t used nav elements here, instead opting for div elements with role="navigation", which is equivalent but doesn’t affect the document outline, so I’d say the above applies.

Using a list also lets screen reader users know up-front how many links the navigation contains and is suggested in the WCAG 2.0 technique H48: Using ol, ul and dl for lists or groups of links.

And finally it is a well-established markup pattern that just feels right, so I’d want a really good reason to use something else.

Posted on October 13, 2013 in CSS, JavaScript

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.