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

HtmlCss

Html Problem Overview


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?

Html Solutions


Solution 1 - Html

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 - Html

###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 - Html

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 - Html

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 - Html

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

Solution 6 - Html

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>

Solution 7 - Html

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

Solution 8 - Html

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.

Solution 9 - Html

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.

Solution 10 - Html

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>

Solution 11 - Html

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.

Solution 12 - Html

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

Solution 13 - Html

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionflightView Question on Stackoverflow
Solution 1 - HtmlAsbjørn UlsbergView Answer on Stackoverflow
Solution 2 - HtmlJosh CrozierView Answer on Stackoverflow
Solution 3 - HtmlremitbriView Answer on Stackoverflow
Solution 4 - HtmlmikelikespieView Answer on Stackoverflow
Solution 5 - HtmlRafał RowińskiView Answer on Stackoverflow
Solution 6 - Htmlbash2dayView Answer on Stackoverflow
Solution 7 - HtmlJordi BunsterView Answer on Stackoverflow
Solution 8 - HtmlflightView Answer on Stackoverflow
Solution 9 - HtmlDiaren WView Answer on Stackoverflow
Solution 10 - HtmlRaksmeyView Answer on Stackoverflow
Solution 11 - HtmlLitekView Answer on Stackoverflow
Solution 12 - HtmlJason PaulView Answer on Stackoverflow
Solution 13 - HtmlDavid HeggieView Answer on Stackoverflow