Styling Navigation Bars
Coding explainers for beginners
The horizontal navigation bar
The examples below demonstrate three common techniques for building navigation bars with CSS. In each case, the markup (HTML) is exactly the same. Only the CSS is different. Visually and functionally, all three are identical.
<nav> <ul> <li><a href="#">Liquorice</a></li> <li><a href="#">Toffee</a></li> <li><a href="#">Marshmallow</a></li> <li><a href="#">Fudge</a></li> <li><a href="#">Nougat</a></li> <li><a href="#">Marzipan</a></li> </ul> </nav>
Semantically, navigation bars are best marked up as unordered lists because they are essentially a list of links. Strictly speaking, no additional markup is required. However, we use the semantic container <nav> to indicate that this list is a part of the navigation for this page. In addition, we should always consider the accessibility of our navigation. We can describe what type of navigation this <nav> element represents for the benefit of assistive technologies (e.g. screen readers). We do this by adding an aria-label attribute. For example, if this is the primary site navigation, we can give the aria-label attribute a value of "primary". This also helps screen reders to differentiate between this element of navigation and any others that might be on the page.
<nav aria-label="primary"> <ul> <li><a href="#">Liquorice</a></li> …
Once we have marked up our navigation bar in this way, a screen reader will describe it to the user as “primary navigation”.
Example 1: Inline
This is probably the oldest technique and involves changing the display property of list items so they display inline and therefore stack up horizontally, left to right. The only glitch with this method is that spaces (or line breaks) in the html cause a 4px gap between the elements. However, this can be removed with a negative 4px margin on the right of each list item.
li { display: inline; margin-right: -4px; }
Example 2: Float
The float technique has the same effect as example 1, providing all list items are floated left. However, we need to overcome the usual collapsing parent element when all child elements are floated. In this case, setting the display property to a value of flow-root does the trick and prevents the <ul> container from collapsing by establishing a new block formatting context.
ul { display: flow-root; } li { float: left; }
Example 3: Flexbox
The flexbox technique is the most recent and also provides the most flexibility when it comes to arranging navigation bar elements, which comes in handy for responsive design. This is what the CSS flexible box module was designed for. There are no major glitches except that flex-wrap should be set to wrap to match the behaviour of the two examples above when the viewport narrows.
ul { display: flex; flex-wrap: wrap; }
Common styling for all three navigation bars
The CSS shown below is common to all the examples on this page. There are a few things worthy of note:
- The anchor element is set to display as an inline block. This means that the whole element becomes clickable rather than just the text and turns what is effectively a text link into a button.
- We have removed the outline on a:focus, which could be an accessibility problem. However, the pseudo-class has been restyled so as to be consistent with the overall design of the navigation bar.
- The order in which the anchor pseudo-classes are written is important because some classes inherit properties from others. The correct order can be remembered using the nemonic LoVe HAte (link, visited, hover, active). The focus pseudo-class is not used as often as it should be, but when it is, it can be grouped with hover or active if the styling is to be the same or as the final pseudo-class of the group if the styling is to be different, as it is in the example below. Remember; focus styles are important to people who use keyboards to navigate websites.
- All five link states perform important functions and should be styled accordingly. Here's a summary of what they do:
- a:link
- The normal link state as it appears before user interaction.
- a:visited
- The link state after it has been clicked. Commonly, this is styled the same as the a:link state.
- a:hover
- The link state when the user moves a mouse over the link. It should be styled to give visual feedback to the user.
- a:active
- The state as it appears when the user clicks the link. Can also be styled to provide visual feedback.
- a:focus
- The state as it appears when the element attains focus. Most commonly, this is used to provide visual feedback for keyboard users (the Tab key is used to cycle through all links on a page).
ul { background-color: #bccfe6; font-weight: 600; } ul a { text-decoration: none; display: inline-block; padding: 15px 20px 12px; border-bottom: 3px solid #bccfe6; } ul a:link { color: #1e5497; } ul a:visited { color: #1e5497; } ul a:hover { color: #c00; background-color: #ffc0cb; border-bottom-color: #987279; } ul a:active { color: #fff; } ul a:focus { outline: none; background-color: #ffc0cb; border-bottom-color: #c00; }
Which to use?
As always, the choice of technique depends on context. All things being equal, flexbox is the best choice for modern web development where you don't need to support older browsers. However, both the float and inline methods are currently more robust because they are supported by older browsers. That aside, the choice depends on how you want your navigation bar to look and behave in the various responsive states. All three methods are valid.
One of the many benefits of flexbox is the control we gain over the arrangement of items inside a flex container. In the example below, the justify-content property is used with a value of space-around to space the flex items evenly within the flex container. This effect, and particularly how it responds as the parent element reduces in width, is almost impossible to achieve by any other method.
ul { display: flex; flex-wrap: wrap; justify-content: space-around; }
A note on selectors
The four navigation bars in this document are all styled differently but no classes or IDs have been used to select them. Instead, the nth-of-type selector is used. So, for example, the third navigation bar can be selected like this:
nav:nth-of-type(3) ul { display: flex; flex-wrap: wrap; }
This rule works by selecting all unordered lists that are children of the third <nav> element in the document. To select the second nav element, just change the value to (2).
One of the many benefits of adding an aria-label attribute to the <nav> element is that it gives us an easy way to select it when styling. We can do this using an attribute selector.
nav[aria-label="primary"] ul { display: flex; flex-wrap: wrap; }
The selector for the rule above will select any <ul> element with a parent <nav> element that has an aria-label attribute and a value of "primary".
The great thing about this technique is that we can easily select and style the primary navigation without using any unessesary markup such as class attributes.