css only horizontal subnav

Curtis picture Curtis · Jan 24, 2013 · Viewed 8.7k times · Source

I am building a CSS only two-level horizontal navigation bar with relative sub-navigation to the parent. All menu items are inline. Dependent upon the classes 'right' or 'left', the sub-nav aligns to the parent. This is what I've managed to accomplish so far:

html:

<body>
    <div class="navbar">
        <ul class="topnav left">
            <li>nav</li>
            <li>menu1
                <span class="subnav">
                    <ul class="subnav subnav-left">
                        <li>item1-1</li>
                        <li>item1-2</li>
                        <li>item1-3</li>
                    </ul>
                </span>
            </li>
            <li>menu2
                <span class="subnav">
                    <ul class="subnav subnav-left">
                        <li>item2-1</li>
                        <li>item2-2</li>
                        <li>item2-3</li>
                    </ul>
                </span>
            </li>
        </ul>
        <ul class="topnav right">
            <li class="right">menu3
                <span class="subnav subnav-right">
                    <ul class="subnav subnav-left">
                        <li>item3-1</li>
                        <li>item3-2</li>
                        <li>item3-3</li>
                    </ul>
                </span>
            </li>
            <li class="right">menu4
                <span class="subnav subnav-right">
                    <ul class="subnav subnav-left">
                        <li>item4-1</li>
                        <li>item4-2</li>
                        <li>item4-3</li>
                    </ul>
                </span>
            </li>
        </ul>
    </div>
</body>

css:

body {
    font-family: arial;
    margin: 0;
}

.navbar {
    height: 40px;
    background-color: black;
}

ul.topnav {
    margin: 0;
    padding: 0;
}

.subnav {
    position: absolute;
}

.subnav-right {
    right: 0;
}

ul.subnav {
    position: relative;
    margin: 4px 0 0 -8px;
    padding: 0;
    display: none;
}

ul.topnav  li{
    list-style: none;
    display: inline-block;
    color: white;
    padding: 4px 8px;
    font-weight: bold;
    line-height: 32px;
    float: left;
    clear: none;
    box-sizing: border-box;
}

ul.subnav li {
    background-color: red;
    list-style: none;
    display: inline-block;
    color: white;
    padding: 4px 8px;
    font-weight: bold;
    position: relative;
    line-height: 32px;
    float: left;
    clear: none;
    box-sizing: border-box;
}

.topnav li:hover {
    background-color: red;
}

.topnav li:hover ul.subnav {
    display: inline-block;
    background-color: red;
}

.nav ul li:hover {
    background-color: black;
}

.nav ul li {
    width: 100%;
}

.nav li ul {
    display: inline-block;
    clear: none;
    position: absolute;
    background-color: red;
    margin: 4px 0 0 -8px;
    padding: 0;
}

.left {
    float: left;
}

.right {
    float: right;
}

The jsfiddle:

jsfiddle.net/aLZqZ

Here is what I'm trying to accomplish:

image to nav menu

Answer

1934286 picture 1934286 · Feb 2, 2013

I got this for you http://jsfiddle.net/aLZqZ/99/. In under 100 tries, too. I became a little obsessed and spent at least 5 hours total. A good challenge for me and I have never really fiddled with sub navs before.

This issue was three fold:

  • Using float:right for a horizontal nav bar is usually not good in my experience because it causes unexpected issues, also it is negated and ignored by browsers if the same element is positioned relative or absolute (you had a lot of superfluous code, btw). I changed float:right to text-align:right where necessary. See this for horizontal nav I fixed for someone recently: Aligning/floating my nav bar to the right
  • The li element containing the sub menu was not positioned, therefore, the position:absolute and right:0 on the ul within it moves according to the closest containing element that is position:absolute or :relative. In this case there was not one so that element was html; thus the ul would be pushed all the way right to the end of the page. I added position:relative to these li elements which then made the right:0 behave as expected, but did not put all the li element on one line and stacked them instead.
  • You had tags with display:inline-block when :inline would have done it, but more importantly, no one ever really mentions that white-space:nowrap on the same elements to do what you are trying here is important. inline-block and nowrap together should force one line block like elements that you can align or float as whole as if they were a paragraph. BTW, IE7 needs some special attention for inline-block. See here: http://robertnyman.com/2010/02/24/css-display-inline-block-why-it-rocks-and-why-it-sucks/
  • I made special css at the bottom of yours in your fiddle to separate the left and right navs, and I basically left your original css alone. I also adjusted the html a bit. Here it all is.

    HTML for the right nav (follows the HTML for the left nav):

            <ul class="rightNav">
                <li>menu3
                        <ul class="rightSubNav">
                            <li>item3-1</li>
                            <li>item3-2</li>
                            <li>item3-3</li>
                        </ul>
                </li>
                <li>menu4
                        <ul class="rightSubNav">
                            <li>item4-1</li>
                            <li>item4-2</li>
                            <li>item4-3</li>
                        </ul>
                </li>
            </ul>
    

    CSS that I added to separate the right and left nav:

            ul.rightNav {
            margin:0;
            padding:0;
            text-align: right;
        }
    
        .rightNav li:hover {
            background-color: red;
        }
    
        ul.rightNav  li{
            list-style: none;
            display: inline-block;
            color: white;
            padding: 4px 8px;
            font-weight: bold;
            line-height: 32px;
            position:relative;
        }
    
        ul.rightSubNav {
            position: absolute;
            right:0;
            margin: 4px 0 0 -20px;
            padding: 0;
            display: none;
            white-space:nowrap;
        }
        ul.rightSubNav li {
            background-color: red;
            list-style: none;
            display: inline;
            color: white;
            padding: 4px 8px;
            font-weight: bold;
            position: relative;
            line-height: 32px;
        }
    
        .rightNav li:hover ul.rightSubNav {
            display: inline-block;
            background-color: red;
        }
    

    If this helped I would appreciate the up votes and answer select. If you figured something else out and got it working differently please post. I would love to see it.