HTML & CSS - - By Dan Rose

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

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.

Sponsors