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

Solution 1:

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.

Solution 2:

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>

Solution 3:

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.

Solution 4:

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>

Solution 5:

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;
}