Responsive, Fluid-Width, Variable-Item Navigation with CSS

Dan Rose

Around six months ago I was asked to develop a single row navigation bar that could contain a variable number of menu items while filling the entire width of the container. The amount of navigational items was dictated elsewhere and likely to change in future.

Thinking ahead, it would have been impractical to change the CSS every time a menu item was added or removed. A resolution had to be found, ideally without using JavaScript.

Using Percentage Widths

Fixed widths are unable to consistently fill the responsive container, as the below image illustrates.

Fixed-width nav example

So we need to use percentages to accomplish this. Percentages are the prevalent method to achieve a fluid grid layout in responsive designs and will come in useful here.

For example, take the below familiar HTML into consideration:

<nav>
  <ul>
    <li><a href="home.html">Home</a></li>
    <li><a href="about.html">About</a></li>
    <li><a href="services.html">Services</a></li>
    <li><a href="products.html">Products</a></li>
    <li><a href="jobs.html">Jobs</a></li>
    <li><a href="contact.html">Contact</a></li>
  </ul>
</nav>

And we’ll add the following CSS:

nav {
    width: 100%;
    background: #f0f0f0;
    border: 1px solid #ccc;
    border-right: none;
}

nav ul {
    overflow: hidden;
    margin: 0;
    padding: 0;
}

nav ul li {
    list-style: none;
    float: left;
    text-align: center;
    border-left: 1px solid #fff;
    border-right: 1px solid #ccc;
    width: 16.6667%; /* fallback for non-calc() browsers */
    width: calc(100% / 6);
    box-sizing: border-box;
}

nav ul li:first-child {
    border-left: none;
}

nav ul li a {
    display: block;
    text-decoration: none;
    color: #616161;
    padding: 10px 0;
}

Areas of note are:

  • The <nav> element is given width: 100% to fill the entire available space.
  • <li> elements are supplied with two width properties – width: calc(100% / 6); and width: 16.6667%;. This is where the menu items are divided across the <nav> element into 6 equal widths. The order these properties are listed is important to allow browsers that support calc() to utilize it over the standard percentage width. Browsers without support for calc() will simply ignore it and use the fallback. I tend to use calc() with percentages to assist in readability and to allow the browser to use the result it is optimized for. That way I don’t have to worry about whether to include recurring decimal places.
  • box-sizing: border-box; is declared on the <li> elements. This forces the width property to include the border-left and border-right values in the total width. Without using this property, the <li> elements wouldn’t fit within the <nav> area and the last <li> element would wrap onto a new line. Many prefer to set box-sizing: border-box; to all elements in order to easily manage widths, as Paul Irish suggests.

The result of adding these styles is shown in the CodePen demo below:

See the Pen Fixed-width Fluid Navigation by SitePoint (@SitePoint) on CodePen.

Open it in a new window and test it out for responsiveness. The desired look is achieved and it’s flexible.

What if We Add or Remove Items?

Here’s what happens if we remove one item from the navigation, with our current CSS in place:

See the Pen Fluid-width by SitePoint (@SitePoint) on CodePen.

As you can see, there is an empty area on the right, where the last nav item was.

How about when an extra nav item is added?

See the Pen Fluid-width Navigation, One Extra Item by SitePoint (@SitePoint) on CodePen.

Now the last navigation item wraps onto a second row.

Clearly, this doesn’t satisfy all our requirements. JavaScript could be used to count the number of menu items, work out the percentage and apply width values by looping through the <li> elements. However, I am always reluctant to use JavaScript to define layout unless there’s no other option. Plus, this wouldn’t be a CSS article if I was going to describe how to do that! Back to the drawing board, it is…

(Re)introducing the Table Layout

Fear not, I am not advocating the use of a 90s throwback HTML table for layout; that would be semantically incorrect, of course. However, there are display values that allow elements to act like a table.

This means that the earlier HTML structure can still be used with the following CSS:

nav {
    display: table;
    table-layout: fixed;
    width: 100%;
}

nav ul {
    display: table-row;
    margin: 0;
    padding: 0;
}

nav ul li {
    list-style: none;
    display: table-cell;
    text-align: center;
}

nav ul li a {
    display: block;
}

Setting the display property allows the <nav> element to act as the table, the <ul> as the row and the <li> as the table cell. Note the inclusion of the table-layout: fixed declaration, which applies an equal width to each menu item. This can be removed, but fluctuating lengths of text can cause the table to look off balance, so approach with caution.

Here’s a demo, with buttons to let you add/remove items:

See the Pen Fluid-width Navigation Using display: table by SitePoint (@SitePoint) on CodePen.

This may not be the cleanest approach (apologies to the table haters), but it works and it’s simple. Browser support is good too.

The Future: Flexbox

Flexbox is the standout candidate to replace this method going forward, as the older versions of Internet Explorer are phased out and browser support for flexbox continues to improve. If you are unfamiliar with flexbox, you can check out this recent article on SitePoint or visit the spec, which explains it as follows:

In the flex layout model, the children of a flex container can be laid out in any direction, and can “flex” their sizes, either growing to fill unused space or shrinking to avoid overflowing the parent. Both horizontal and vertical alignment of the children can be easily manipulated.

Sounds ideal to resolve my problem.

Again, using the earlier HTML, here is the CSS to construct the flexbox approach to solving this problem:

nav {
    width: 100%;
}

nav ul {
    display: flex;
    flex-direction: row;
    margin: 0;
    padding: 0;
}

nav ul li {
    list-style: none;
    flex-grow: 1;
    text-align: center;
}

nav ul li a {
    display: block;
}

The flexbox properties to note are:

  • display: flex; This is applied to the <ul> element and enables a flex environment for its children.
  • flex-direction: row; is assigned to the <ul> to position it’s children in a left to right format. This is the default value so it isn’t explicitly necessary.
  • flex-grow: 1; is the magic that equally spreads the <li> elements across the container. If you were to give a specific <li> a value of 2, that element would grow to twice the size of the others.

Flexbox has some interesting features that may be appropriate in other use cases, such as the ability to reorder menu items by declaring the order property.

Here’s our final demo using Flexbox:

See the Pen Fluid-width Navigation Using Flexbox by SitePoint (@SitePoint) on CodePen.

As you can see in the above CodePen demo, the same result is achieved. Again, you can use the buttons to test out adding or removing a single item. You can also add more items by editing the HTML. And by resizing your browser window you can see it’s fluidity.

If you’ve ever had to solve this issue, let us know how you accomplished it and what technique you used.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • LouisLazaris

    Yeah, and probably “responsive” is not the best choice for the title, that was my addition. But there are very easy ways to get it working on handheld devices.

    The main idea here was to get it working fluid, at full-width. Maybe I’ll adjust the demos to include a media query that drops the items vertically or something, but I’m sure anyone can solve that problem without extra help. Or use a hamburger icon or whatever.

    • Kevdev

      Louis, with all due respect – you folks at Sitepoint are great – the misuse of the word ‘responsive’ is one of my pet peeves.

      Responsive means that it responds well _to_your_actions; like responsive acceleration in a car or like a ‘peppy’ web site that is not slow when you click around in it.

      Responsive does not mean that the page has the ability to resize itself for a smartphone. Yes, I know it was probably first used to say that the ‘page responds well to all device screen sizes’. But without that specific context, ‘responsive’ could be about any characteristic of your browsing device (processor type, memory size, outside temperature …) and we would just have to guess what they are referring to.

      That is why when I read the title to this article, I was not even thinking about screen size at all.

      Sorry to get off topic … I, I, I just couldn’t hold it back any longer … :)

      • LouisLazaris

        Point noted, but I kind of disagree on your definition of “responsive”. Because if that’s the case, then the entire community is wrong about the way they use the word.

        “Responsive” is in fact in reference to how the page responds to the various device and window sizes, not to the way the user responds.

        All that being said, however, I agree, the community is using that word wrong, and probably “Adaptive” should be the word to describe this (which it often is).

        • Kevdev

          ‘Adaptive’ is great – it describes it so much better!

          And yes, that was my point – the entire community IS wrong about the way they use the word ‘responsive’. Perhaps with Sitepoint’s powerful presence you can sway everyone into using ‘adaptive’.

          Cheers.

          • LouisLazaris

            I agree, but unfortunately, we’re stuck with the term ‘responsive’. For example, one of our recent articles discussed the difference between RWD and “adaptive”:

            http://www.sitepoint.com/moving-beyond-responsive-web-adaptive-web/

            And there is a well known book on the topic of adaptive design, but I don’t know what that one discusses as far as details.

            I’m not too concerned about terminology, except in one area: What it means to clients, which can be confusing to them. And Craig Buckler discussed that problem here:

            http://www.sitepoint.com/mobile-first-considered-confusing/

            Anyhow, thanks for the feedback!

  • Ralph Mason

    Useful article. As an aside, though “apologies to the table haters”? It’s HTML table layout that people might object to, but that’s completely different from CSS `display: table`.

    • Dan Rose

      Thanks for your feedback. I agree that it is HTML table layouts that are met with some level of indignation. My comment was merely an attempt to add some light humor to the table subject. As I pointed out, by using the CSS display properties you are avoiding use of the incorrect HTML semantics of a table structure for a navigation area. Of course it is still necessary to apply CSS properties to the different elements to adopt that tabular behavior.

      • Ralph Mason

        No worries, Louis and Dan. I hope I didn’t sound critical. Maybe the problem is in calling them “CSS tables” … although I guess it’s an accurate description. But display: table is very handy in a lot of situations, so I like to talk it up a bit. :) Can’t wait for display: flex to take off, though.

    • LouisLazaris

      That’s true, Ralph, but many people feel that if you use table layout via CSS, then you might as well use regular tables. It’s kind of a purist thing, and that’s probably what Dan was referring to, and why I left the comment in when editing this article. In other words, some people feel there is no difference between the two, but that’s not really the case (for semantics and accessibility reasons, to name a few).

  • http://alfy.me/ Ahmad Alfy

    What do you think of using Modernizr to test for flexbox and if it is not supported fall back to table display? Or is it just useless and we shall drop table properties and go straight for flexbox?

    .flexbox nav { display: flex; } …

    .no-flexbox nav { display: table; }

    • Dan Rose

      Funnily enough, in my first draft I touched upon using Modernizr for that exact purpose. In my use case browser support demanded the need for the tabular approach. Having said that, planning for the future can only be a good thing which is what Modernizr allows us to do. If I was to create a similar navigation bar right now, personally I would use Modernizr to provide both approaches. I think that because navigation is such a crucial component of a website it is a necessity to at least do the tabular approach though. There is an article on SitePoint that discusses whether we’re ready for Flexbox now which you may like to read (I tried to link to it previously but it didn’t pass moderation). It’s still open to interpretation and it largely depends on the browsers you need to support.

      • http://alfy.me/ Ahmad Alfy

        I found the article you are mentioning, thanks for pointing that.
        I think that it is as you stated, it’s subjected to the browsers we want to support.

    • George Gooding

      Haven’t double-checked, but I think you can just declare the table-based CSS values on display first, and then flex after, which should result in browsers that don’t understand flex box to hang on to the display table values? Even if you set display: table-cell on the “cells”, that will be ignored if the browser understands flex box and sets the container to flex. I think. Please double check! :)

      • http://alfy.me/ Ahmad Alfy

        Great idea. definitely worth to check

  • Theracoon

    I switched our sites to the table layout method a few years ago because people kept adding and removing links to our top nav and I had to keep updating our CSS to match which was very annoying. Another method I used in a few places when I have to have even width blocks involves counting child elements using the last-child selector.

    nav li {display: inline-block; font-size: 0; box-sizing: border-box;
    & a {font-size: 13px;}
    &:first-child:nth-last-child(5),
    &:first-child:nth-last-child(5) ~ li {width: 20%;}
    &:first-child:nth-last-child(4),
    &:first-child:nth-last-child(4) ~ li {width: 25%;}
    &:first-child:nth-last-child(3),
    &:first-child:nth-last-child(3) ~ li {width: 33.3%;}
    &:first-child:nth-last-child(2),
    &:first-child:nth-last-child(2) ~ li {width: 50%;}
    &:first-child:nth-last-child(1),
    &:first-child:nth-last-child(1) ~ li {width: 100%;}
    }

  • George Gooding

    What about using flex wrap? Then you can stack the menu vertically, if that’s OK.

  • Riccardo Zanutta

    What if the navbar has a fixed width?
    You have to remove width: 100% but layout breaks.