How do I *really* justify a horizontal menu in HTML+CSS?
Asked Answered
G

14

88

You find plenty of tutorials on menu bars in HTML, but for this specific (though IMHO generic) case, I haven't found any decent solution:

#  THE MENU ITEMS    SHOULD BE    JUSTIFIED     JUST AS    PLAIN TEXT     WOULD BE  #
#  ^                                                                             ^  #
  • There's an varying number of text-only menu items and the page layout is fluid.
  • The first menu item should be left-aligned, the last menu item should be right-aligned.
  • The remaining items should be spread optimally on the menu bar.
  • The number is varying,so there's no chance to pre-calculate the optimal widths.

Note that a TABLE won't work here as well:

  • If you center all TDs, the first and the last item aren’t aligned correctly.
  • If you left-align and right-align the first resp. the last items, the spacing will be sub-optimal.

Isn’t it strange that there is no obvious way to implement this in a clean way by using HTML and CSS?

Geoff answered 8/9, 2008 at 11:57 Comment(0)
H
44

Modern Approach - Flexboxes!

Now that CSS3 flexboxes have better browser support, some of us can finally start using them. Just add additional vendor prefixes for more browser coverage.

In this instance, you would just set the parent element's display to flex and then change the justify-content property to either space-between or space-around in order to add space between or around the children flexbox items.

Using justify-content: space-between - (example here):

ul {
    list-style: none;
    padding: 0;
    margin: 0;
}
.menu {
    display: flex;
    justify-content: space-between;
}
<ul class="menu">
    <li>Item One</li>
    <li>Item Two</li>
    <li>Item Three Longer</li>
    <li>Item Four</li>
</ul>

Using justify-content: space-around - (example here):

ul {
    list-style: none;
    padding: 0;
    margin: 0;
}
.menu {
    display: flex;
    justify-content: space-around;
}
<ul class="menu">
    <li>Item One</li>
    <li>Item Two</li>
    <li>Item Three Longer</li>
    <li>Item Four</li>
</ul>
Hart answered 21/3, 2015 at 21:51 Comment(1)
The justify-content: space-around solution doesn't insert any line breaks if the menu doesn't fit.Tutto
T
84

The simplest thing to do is to is to force the line to break by inserting an element at the end of the line that will occupy more than the left available space and then hiding it. I've accomplished this quite easily with a simple span element like so:

#menu {
  text-align: justify;
}

#menu * {
  display: inline;
}

#menu li {
  display: inline-block;
}

#menu span {
  display: inline-block;
  position: relative;
  width: 100%;
  height: 0;
}
<div id="menu">
  <ul>
    <li><a href="#">Menu item 1</a></li>
    <li><a href="#">Menu item 3</a></li>
    <li><a href="#">Menu item 2</a></li>
  </ul>
  <span></span>
</div>

All the junk inside the #menu span selector is (as far as I've found) required to please most browsers. It should force the width of the span element to 100%, which should cause a line break since it is considered an inline element due to the display: inline-block rule. inline-block also makes the span possible to block-level style rules like width which causes the element to not fit in line with the menu and thus the menu to line-break.

You of course need to adjust the width of the span to your use case and design, but I hope you get the general idea and can adapt it.

Tantalous answered 14/3, 2010 at 22:49 Comment(16)
It seems to work if you use a span instead of an hr! It's not really working, the HR is occupying visible space - use #menu { border: solid 1px green; } to confirm. Also, display: inline-block; does not work on IE (...7? CompatibilityView?) for elements that aren't naturally inline elements. HR is a block element, so I'm guessing inline-block doesn't work for HR on IE. Anyway, span.Tehee
Also, make it width: 100%; instead of width: 900px;. Edit that and switch the hr for a span and you'll get an upvote - and most likely marked as answer. ;)Tehee
I haven't tested my changes, but I think my solution should reflect your thoughts now.Dahle
@sr pt correct IE7 and lower. IE7 compatibility should not work either.Roam
works great, but results are prettier if you replace each space with &ensp; (n space), so that Menu & item & 1 stay close together. &nbsp; doesn't work in safari.Vaishnava
@yitwail, which spaces are you thinking about? With your reputation, I guess you should be able to edit my answer and make your suggested improvements?Dahle
Note that you don't need to have an extra <span> element, you can also use the :after pseudoclass and the content property to have the same effect. Probably won't work in IE < 9 though.Aneroidograph
Or instead replacing spaces with &nbsp;'s you can set text-align:left; on items.Paripinnate
I just spent over an hour beating my head against the wall, trying to figure out why this wasn't working for me. The answer is that I needed white space between the tags. I am working in WordPress, and wp_page_menu does not include line breaks after each <li></li>. Added them using preg_replace, and it works now. FwewwwGhat
To get this to work these days you have to use display: inline-block on the LIs in browsers that support it, and use display: inline with spaces replaced by &nbsp; on browsers that don't support it. Also since ie7 doesn't support inline-block, you have to use inline on your force-wrap tag and just shove enough &nbsp;s in it to force the wrap.Crisscross
@Joren, thanks for your suggestions! Please feel free to update the answer with your suggestions, or send the updated code example to me so I can do it.Dahle
To clarify Joren's comment, the UL still needs to be inline and the LI's inline-block.Neelyneeoma
This works great! However, I'm failing to add top/bottom paddings to my li's. The padding is hidden as overflow. Any way to add visible padding to the items?Galasyn
Looks great! What is the purpose of the position: relative; style on the span tag though?Obduliaobdurate
Important note: there must be blank characters (\t or \n or space) between li elements. This means inside the template .. ?><li>…</li><?php will prevent this trick from working. Add a blank char between </li> and <?phpLudicrous
Hmm, this is nice, but inserts extra vertical space after the menu. Not sure how to remove it.Tutto
H
44

Modern Approach - Flexboxes!

Now that CSS3 flexboxes have better browser support, some of us can finally start using them. Just add additional vendor prefixes for more browser coverage.

In this instance, you would just set the parent element's display to flex and then change the justify-content property to either space-between or space-around in order to add space between or around the children flexbox items.

Using justify-content: space-between - (example here):

ul {
    list-style: none;
    padding: 0;
    margin: 0;
}
.menu {
    display: flex;
    justify-content: space-between;
}
<ul class="menu">
    <li>Item One</li>
    <li>Item Two</li>
    <li>Item Three Longer</li>
    <li>Item Four</li>
</ul>

Using justify-content: space-around - (example here):

ul {
    list-style: none;
    padding: 0;
    margin: 0;
}
.menu {
    display: flex;
    justify-content: space-around;
}
<ul class="menu">
    <li>Item One</li>
    <li>Item Two</li>
    <li>Item Three Longer</li>
    <li>Item Four</li>
</ul>
Hart answered 21/3, 2015 at 21:51 Comment(1)
The justify-content: space-around solution doesn't insert any line breaks if the menu doesn't fit.Tutto
M
12

Ok, this solution doesn't work on IE6/7, because of the lack of support of :before/:after, but:

ul {
  text-align: justify;
  list-style: none;
  list-style-image: none;
  margin: 0;
  padding: 0;
}
ul:after {
  content: "";
  margin-left: 100%;
}
li {
  display: inline;
}
a {
  display: inline-block;
}
<div id="menu">
  <ul>
    <li><a href="#">Menu item 1</a></li>
    <li><a href="#">Menu item 2</a></li>
    <li><a href="#">Menu item 3</a></li>
    <li><a href="#">Menu item 4</a></li>
    <li><a href="#">Menu item 5</a></li>
  </ul>
</div>

The reason why I have the a tag as an inline-block is because I don't want the words inside to be justified as well, and I don't want to use non-breaking spaces either.

Mannose answered 4/3, 2012 at 23:30 Comment(3)
Thanks! Your soultion is down to the point. If I were to initiate the question, I would mark it as an answer.Agog
I had problems getting this to work in IE. After some hours spent in Google land I finally found this blog post: kristinlbradley.wordpress.com/2011/09/15/… What finally did the trick was to add text-justify: distribute-all-lines; to the ul selector. Seems to be proprietary IE stuff.Desiccated
Not sure if this matters, but I used width: 100% instead of margin-left: 100%: ul:after{content:""; margin-left:100%;}Krever
C
8

Got a solution. Works in FF, IE6, IE7, Webkit, etc.

Make sure you don't put any whitespace before closing the span.inner. IE6 will break.

You can optionally give .outer a width

.outer {
  text-align: justify;
}
.outer span.finish {
  display: inline-block;
  width: 100%;
}
.outer span.inner {
  display: inline-block;
  white-space: nowrap;
}
<div class="outer">
  <span class="inner">THE MENU ITEMS</span>
  <span class="inner">SHOULD BE</span>
  <span class="inner">JUSTIFIED</span>
  <span class="inner">JUST AS</span>
  <span class="inner">PLAIN TEXT</span>
  <span class="inner">WOULD BE</span>
  <span class="finish"></span>
</div>
Cherokee answered 17/3, 2010 at 0:13 Comment(4)
For me, this solution works fine with Gecko, but not with WebKit browsers (tested with Chromium, Midori, Epiphany): With WebKit, there's trailing space after the last item.Geoff
Make sure there's only one character of whitespace between each span, and two between the last inner and the finish. If that doesn't work fiddle around with the whitespace. There is a bug in webkit.Cherokee
.outer {text-align: justify} .outer span.finish {display: inline-block; width: 100%} .outer span.inner {display: inline-block; white-space: nowrap} Does not work on IE6 and IE7.Ornithic
Giving the .outer line-height: 0 forces the list's height to be rendered as if it were consisting of one line.Antherozoid
A
3

Works with Opera , Firefox, Chrome and IE

ul {
   display: table;
   margin: 1em auto 0;
   padding: 0;
   text-align: center;
   width: 90%;
}

li {
   display: table-cell;
   border: 1px solid black;
   padding: 0 5px;
}
Apogeotropism answered 27/7, 2012 at 12:6 Comment(1)
Unfortunately this does not work in IE 7 because of the missing table-cell supportDecode
O
3

yet another solution. I had no option to tackle the html like adding distinguished class etc., so I found a pure css way.

Works in Chrome, Firefox, Safari..don't know about IE.

Test: http://jsfiddle.net/c2crP/1

ul {
  margin: 0; 
  padding: 0; 
  list-style: none; 
  width: 200px; 
  text-align: justify; 
  list-style-type: none;
}
ul > li {
  display: inline; 
  text-align: justify; 
}

/* declaration below will add a whitespace after every li. This is for one line codes where no whitespace (of breaks) are present and the browser wouldn't know where to make a break. */
ul > li:after {
  content: ' '; 
  display: inline;
}

/* notice the 'inline-block'! Otherwise won't work for webkit which puts after pseudo el inside of it's parent instead of after thus shifting also the parent on next line! */
ul > li:last-child:after {
  display: inline-block;
  margin-left: 100%; 
  content: ' ';
}
<ul>
  <li><a href="#">home</a></li>
  <li><a href="#">exposities</a></li>
  <li><a href="#">werk</a></li>
  <li><a href="#">statement</a></li>
  <li><a href="#">contact</a></li>
</ul>
Overarch answered 19/10, 2013 at 19:14 Comment(0)
A
2

Make it a <p> with text-align: justify ?

Update: Nevermind. That doesn't work at all as I'd thought.

Update 2: Doesn't work in any browsers other than IE right now, but CSS3 has support for this in the form of text-align-last

Anu answered 8/9, 2008 at 11:58 Comment(2)
That was fast! I thought my own solution was unique, but you came up with something similar to start with in just a few moments.Geoff
Now in 2016 text-align-last works in any browsers other than Safari. This should be the possible opponent of the flexbox approach or the span hack.Javier
G
1

For Gecko-based browsers, I came up with this solution. This solution doesn't work with WebKit browsers, though (e.g. Chromium, Midori, Epiphany), they still show trailing space after the last item.

I put the menu bar in a justified paragraph. Problem is that the last line of a justified paragraph won't be rendered justified, for obvious reasons. Therefore I add a wide invisible element (e.g. an img) which warrants that the paragraph is at least two lines long.

Now the menu bar is justified by the same algorithm the browser uses for justifying plain text.

Code:

<div style="width:500px; background:#eee;">
 <p style="text-align:justify">
  <a href="#">THE&nbsp;MENU&nbsp;ITEMS</a>
  <a href="#">SHOULD&nbsp;BE</a>
  <a href="#">JUSTIFIED</a>
  <a href="#">JUST&nbsp;AS</a>
  <a href="#">PLAIN&nbsp;TEXT</a>
  <a href="#">WOULD&nbsp;BE</a>
  <img src="/Content/Img/stackoverflow-logo-250.png" width="400" height="0"/>
 </p>
 <p>There's an varying number of text-only menu items and the page layout is fluid.</p>
 <p>The first menu item should be left-aligned, the last menu item should be right-aligned. The remaining items should be spread optimal on the menu bar.</p>
 <p>The number is varying,so there's no chance to pre-calculate the optimal widths.</p>
 <p>Note that a TABLE won't work here as well:</p>
 <ul>
  <li>If you center all TDs, the first and the last item aren't aligned correctly.</li>
  <li>If you left-align and right-align the first resp. the last items, the spacing will be sub-optimal.</li>
 </ul>
</div>

Remark: Do you notice I cheated? To add the space filler element, I have to make some guess about the width of the menu bar. So this solution is not completely down to the rules.

Geoff answered 8/9, 2008 at 12:19 Comment(3)
Note that you could have used a list here as well, if you set display:inline on the list-items... lists are more conventional for menus.Hush
@Andrew Vit: You're right. That's mostly what asbjornu's solution suggests as well.Geoff
@flight, if you think my answer is a solution can you please mark it as one? Right now it looks like your problem is unsolved. If it isn't, it would be nice if you provided us with the solution you've found or mark any of the provided answers as the solution to your problem. :-)Dahle
A
1

Text is only justified if the sentence naturally causes a line break. So all you need to do is naturally force a line break, and hide whats on the second line:

CSS:

ul {
  text-align: justify;
  width: 400px;
  margin: 0;
  padding: 0;
  height: 1.2em;
  /* forces the height of the ul to one line */
  overflow: hidden;
  /* enforces the single line height */
  list-style-type: none;
  background-color: yellow;
}

ul li {
  display: inline;
}

ul li.break {
  margin-left: 100%;
  /* use e.g. 1000px if your ul has no width */
}

HTML:

<ul>
  <li><a href="/">The</a></li>
  <li><a href="/">quick</a></li>
  <li><a href="/">brown</a></li>
  <li><a href="/">fox</a></li>
  <li class="break">&nbsp;</li>
</ul>

The li.break element must be on the same line as the last menu item and must contain some content (in this case a non breaking space), otherwise in some browsers, if it's not on the same line then you'll see some small extra space on the end of your line, and if it contains no content then it's ignored and the line is not justified.

Tested in IE7, IE8, IE9, Chrome, Firefox 4.

Aright answered 22/7, 2011 at 11:35 Comment(0)
F
0

if to go with javascript that is possible (this script is base on mootools)

<script type="text/javascript">//<![CDATA[
    window.addEvent('load', function(){
        var mncontainer = $('main-menu');
        var mncw = mncontainer.getSize().size.x;
        var mnul = mncontainer.getFirst();//UL
        var mnuw = mnul.getSize().size.x;
        var wdif = mncw - mnuw;
        var list = mnul.getChildren(); //get all list items
        //get the remained width (which can be positive or negative)
        //and devided by number of list item and also take out the precision
        var liwd = Math.floor(wdif/list.length);
        var selw, mwd=mncw, tliw=0;
        list.each(function(el){
            var elw = el.getSize().size.x;
            if(elw < mwd){ mwd = elw; selw = el;}
            el.setStyle('width', elw+liwd);
            tliw += el.getSize().size.x;
        });
        var rwidth = mncw-tliw;//get the remain width and set it to item which has smallest width
        if(rwidth>0){
            elw = selw.getSize().size.x;
            selw.setStyle('width', elw+rwidth);
        }
    });
    //]]>
</script>

and the css

<style type="text/css">
    #main-menu{
        padding-top:41px;
        width:100%;
        overflow:hidden;
        position:relative;
    }
    ul.menu_tab{
        padding-top:1px;
        height:38px;
        clear:left;
        float:left;
        list-style:none;
        margin:0;
        padding:0;
        position:relative;
        left:50%;
        text-align:center;
    }
    ul.menu_tab li{
        display:block;
        float:left;
        list-style:none;
        margin:0;
        padding:0;
        position:relative;
        right:50%;
    }
    ul.menu_tab li.item7{
        margin-right:0;
    }
    ul.menu_tab li a, ul.menu_tab li a:visited{
        display:block;
        color:#006A71;
        font-weight:700;
        text-decoration:none;
        padding:0 0 0 10px;
    }
    ul.menu_tab li a span{
        display:block;
        padding:12px 10px 8px 0;
    }
    ul.menu_tab li.active a, ul.menu_tab li a:hover{
        background:url("../images/bg-menutab.gif") repeat-x left top;
        color:#999999;
    }
    ul.menu_tab li.active a span,ul.menu_tab li.active a.visited span, ul.menu_tab li a:hover span{
        background:url("../images/bg-menutab.gif") repeat-x right top;
        color:#999999;
    }
</style>

and the last html

<div id="main-menu">
    <ul class="menu_tab">
        <li class="item1"><a href="#"><span>Home</span></a></li>
        <li class="item2"><a href="#"><span>The Project</span></a></li>
        <li class="item3"><a href="#"><span>About Grants</span></a></li>
        <li class="item4"><a href="#"><span>Partners</span></a></li>
        <li class="item5"><a href="#"><span>Resources</span></a></li>
        <li class="item6"><a href="#"><span>News</span></a></li>
        <li class="item7"><a href="#"><span>Contact</span></a></li>
    </ul>
</div>
Frankly answered 27/8, 2010 at 6:47 Comment(0)
P
0

Simpler markup, tested in Opera, FF, Chrome, IE7, IE8:

<div class="nav">
  <a href="#" class="nav_item">nav item1</a>
  <a href="#" class="nav_item">nav item2</a>
  <a href="#" class="nav_item">nav item3</a>
  <a href="#" class="nav_item">nav item4</a>
  <a href="#" class="nav_item">nav item5</a>
  <a href="#" class="nav_item">nav item6</a>
  <span class="empty"></span>
</div>

and css:

.nav {
  width: 500px;
  height: 1em;
  line-height: 1em;
  text-align: justify;
  overflow: hidden;
  border: 1px dotted gray;
}
.nav_item {
  display: inline-block;
}
.empty {
  display: inline-block;
  width: 100%;
  height: 0;
}

Live example.

Paripinnate answered 18/5, 2012 at 11:42 Comment(0)
A
0

try this

*{
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }
    ul {
        list-style: none;
        display: flex;
        align-items: center;
        justify-content: space-evenly;
    }
    <ul>
        <li>List item One</li>
        <li>List item Two</li>
        <li>List item Three </li>
        <li>List item Four</li>
    </ul>
Ataxia answered 26/6, 2022 at 16:26 Comment(0)
J
-1

This can be achieved perfectly by some careful measurements and the last-child selector.

ul li {
margin-right:20px;
}
ul li:last-child {
margin-right:0;
}
Jarvis answered 19/8, 2011 at 5:5 Comment(1)
Keep in mind text doesn't render the same everywhere, a pixel off and you'd see a broken menu. This would only work with fixed-width element like images.Selfacting
K
-2

I know the original question specified HTML + CSS, but it didn't specifically say no javascript ;)

Trying to keep the css and markup as clean as possible, and as semantically meaningful as possible to (using a UL for the menu) I came up with this suggestion. Probably not ideal, but it may be a good starting point:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>

    <head>
        <title>Kind-of-justified horizontal menu</title>

        <style type="text/css">
        ul {
            list-style: none;
            margin: 0;
            padding: 0;
            width: 100%;
        }

        ul li {
            display: block;
            float: left;
            text-align: center;
        }
        </style>

        <script type="text/javascript">
            setMenu = function() {
                var items = document.getElementById("nav").getElementsByTagName("li");
                var newwidth = 100 / items.length;

                for(var i = 0; i < items.length; i++) {
                    items[i].style.width = newwidth + "%";
                }
            }
        </script>

    </head>

    <body>

        <ul id="nav">
            <li><a href="#">first item</a></li>
            <li><a href="#">item</a></li>
            <li><a href="#">item</a></li>
            <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
            <li><a href="#">last item</a></li>
        </ul>

        <script type="text/javascript">
            setMenu();
        </script>

    </body>

</html>
Knitting answered 8/9, 2008 at 13:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.