November 26th, 2007 - by Paul OB

If you look at most restaurant menus (or recipes) you will see the dish described on the left hand side followed by a dotted line that continues to the right side of the menu where the price is situated. Have a look at Figure 1 to see what I mean.

Figure 1

Although this may look like an easy thing to replicate in HTML it’s really not as straight forward as it looks and there are a few obstacles to overcome if we want to reproduce this effect. So that’s exactly what we are going to learn how to do in this article!

Dotted Leaders

As this is clearly a list of sorts, CSS can handle this nicely (although an argument could be made for using tables also). As this column is about CSS we will continue with the CSS method. The most semantic element to use for this is obviously a list; so that’s what we will use.

There are two main issues to overcome:

First we must place the text on both the left and right sides of the same line.

The second problem we will need to overcome is how to make the dotted line continue from the text on the left and stop at the text on the right. This would be simple if the dotted line was underneath the text as we could simply use a border on the list. However, that’s not the way that these menus are usually presented so we are not going to cheat on this.

Left and Right

We’ll tackle the positioning of the text first. In order to have the price on the right hand side we will need to make use of the float property and either float the price to the right, or float the descriptive text to the left and let the price be aligned by text-align:right. Or alternatively float each part left and right. Any of those methods would work but to keep things simple we are going to float both elements; one left and one right.

In order to isolate each section of text we will need some hooks in our mark-up and for this purpose we are going to use an em for the descriptive text and a span for the price. This allows us to target them uniquely within the list item without needing to resort to adding any extra classes at all. You should always try to minimize your mark-up like this and when the chance arises target the element via its situation rather than just tagging another class to it.

Here is the basic HTML for the list element:

  1. <ul><li><em>Some menu text</em><span>£9.99</span></li></ul>

Assuming our list container has sufficient width for this to happen we will just float the em left and float the span to the right.

  1. #wrap li span{
  2. float:right;
  3. }
  4. #wrap li em{
  5. float:left;
  6. font-style:normal;
  7. }

As the contents of the list are floated they will float out of the list parent unless we stop them in some way. We can do this quite simply by floating the list also, which will make it contain its child floats (see the previous article on clearing if you are not sure about this topic. So we will float the ul thus containing all children.

  1. #wrap li{
  2. width:100%;
  3. float:left
  4. }

That will give the initial alignment that we wanted and with a little extra mark-up we soon have the effect shown in Figure 2.

Figure 2

I won’t bore you with the full code as you can grab that from the live demo in the link at the end of the article. It’s just basic CSS so far using an unordered list and then floating the elements as described above. As an aside (and as a matter of interest) I have been asked how I made the shadow effect around the element and this can be seen more clearly in Figure 3.

Figure 3

This is a simple technique and involves using a wrapper that has both a background color and a border in suitable colors. Nested inside this wrapper is our main element which has its own border and background color applied. We simply offset the inner element using relative positioning to reveal the background of the element underneath in the gap that we created by moving it. Relative positioning moves the element without affecting the flow of the document at all and leaves the parent in place creating an offset effect.

  1. #wrap{
  2. width:500px;
  3. border:1px solid #eff2df;
  4. margin:0 20px;
  5. float:left;
  6. background:#809900;
  7. }
  8. #wrap ul{
  9. padding:20px 40px;
  10. list-style:none;
  11. float:left;
  12. border:1px solid #4c7300;
  13. position:relative;
  14. left:-2px;
  15. top:-2px;
  16. background:#eff2df;
  17. color:#4c7300;
  18. }

Although the ul element doesn’t have a width it gets stretched to full width by its children; but normally you would keep the element static or give it a width. By using suitable colors you can get quite a good shadow effect with little effort. The border of the wrap should be a lighter color than the background and adds a nice finishing touch to the effect.

Join The Dots

Getting back on track — our next step is to create the dotted lines that join the left and right sections of text together. We can achieve a full length dotted underline by simply adding a dotted line to the bottom border of each list item.

  1. #wrap li{
  2. border-bottom:1px dotted #000;
  3. float:left
  4. }

Figure 4

That’s looking pretty close but now we have to hide the dotted line at each end (left and right) and move the dotted line in a better horizontal position with the text. This can be accomplished relatively easily by using relative positioning (yet again) and moving the text down on top of the dotted line at each end. Because relative position will not affect the flow of the document, the dotted line will not move away from us as it would have done had we used a margin to move the text downwards. (For more information on relative positioning see our previous article “Relatives who Needs Them“.

You may be thinking here that a few pixels movement would be all that’s needed but that would be very short-sighted of us. If we move the text down with a pixel measurement then as soon as the user resizes the text from the browser controls, the dotted line would become visible again. We need to use an em measurement so that the distance moved also increases (or decreases) relative to the text size selected and ensuring our dotted line remains correctly hidden at each end.

Therefore we move the span and the em downwards resulting in covering the dotted line at each end, just as we wanted.

Well that’s not entirely true!

The text is now on top of the dotted line but is not actually hiding it. In order to hide the dotted line we need to give the em and the span a background color to hide the dots completely. This color needs to be the current background color in order to provide a seamless join. We will also take this opportunity to add a little padding to the elements to tidy the effect up resulting in the following code.

  1. * {margin:0;padding:0}
  2. h1,h2{padding:10px 20px 0}
  3. #wrap{
  4. width:500px;
  5. border:1px solid #eff2df;
  6. margin:0 20px;
  7. float:left;
  8. background:#809900;
  9. }
  10. #wrap ul{
  11. padding:20px 40px;
  12. list-style:none;
  13. float:left;
  14. border:1px solid #4c7300;
  15. position:relative;
  16. left:-2px;
  17. top:-2px;
  18. background:#eff2df;
  19. color:#4c7300;
  20. }
  21. #wrap li{
  22. border-bottom:1px dotted #000;
  23. line-height:1.0;
  24. margin:0 0 .5em 0;
  25. position:relative;
  26. width:100%;
  27. float:left
  28. }
  29. #wrap li span{
  30. background:#eff2df;
  31. padding:1px 0 1px 5px;
  32. float:right;
  33. color:#000;
  34. position:relative;
  35. top:.2em;
  36. }
  37. #wrap li em{
  38. float:left;
  39. margin:0;
  40. position:relative;
  41. top:.2em;
  42. padding:0 5px 0 0;
  43. background:#eff2df;
  44. font-style:normal;
  45. }
  1. <div id="wrap">
  2. <ul>
  3. <li><em>Some text</em><span>£9.99</span></li>
  4. <li><em>Some text a bit longer</em><span>£9.99</span></li>
  5. <li><em>More text</em><span>£10.00</span></li>
  6. <li><em>Other text a bit longer</em><span>£11.00</span></li>
  7. <li><em>text</em><span>£12.00</span></li>
  8. <li><em>Some text a bit longer</em><span>£9.99</span></li>
  9. <li><em>More text</em><span>£10.00</span></li>
  10. <li><em>Other text a bit longer</em><span>£11.00</span></li>
  11. <li><em>text</em><span>£12.00</span></li>
  12. </ul>
  13. </div>

Figure 5 shows the result of the above code:

Figure 5

You can see a live demo here.

I have used the universal global reset method for this demo (*{margin:0;padding:0}) but I would advise using a more thorough reset method so that form elements are not affected. See a previous article for more information on this topic.

Recipe for Disaster

That’s quite a good little effect for relatively simple code. You can resize the text up and down and the layout still holds together quite nicely. If we were feeling lazy we could call it a day here as we seem to have created the effect we wanted. However, looking further ahead we need to think about what happens when the descriptive text is much longer or even runs to two or three lines. What is going to happen to our layout then?

We can soon test that out by adding more text to one of the lines. You can see the result in Figure 6 below.

Figure 6

Although our example is still readable it’s not a very good effect and ruins our nicely presented list. What we really want to achieve here is for the text to wrap to a new line and the dotted line to stay in position on the bottom line of text. We also want the price on the right hand side to stay at the right end of the dotted line. The effect we are looking for can be seen in Figure 7 below.

Figure 7

The descriptive text wraps to a new line and the dotted line continues directly to the price on the right. Although this looks as though it will be quite simple, it in fact turns up that there are a number of new obstacles to overcome. Firstly, look back at Figure 6 and see if you can understand why the dotted line has moved away from the text and not stayed in line with it? You may have thought that the text would just wrap at the end of the line and everything would be fine.

The problem is that we have floated both elements which means that should our content stretch, the element will eventually fill all the available 100% width thus pushing the other float out of the way and onto a new line. That’s exactly what has happened in Figure 6 where the descriptive text has stretched all the way along the line pushing the price out of the way. If you are wondering why the price seems to have dropped down two lines that is because a float is a rectangular box and therefore the float on the right (the price) must clear the whole rectangular block that the other left float creates.

If we outline the float in Figure 6 using a red border it soon becomes clear and easier to understand (Figure 8).

Figure 8

When the width of the left float becomes 100% it shoves the right float down below it thus also pushing the dotted line down and away. Again, it might be thought that an easy solution would be to give the descriptive text a width that stops it from reaching the float on the right. We could do this quite easily and allow enough room (to cover normal situations) for the text on the right. However, as can be seen in Figure 9 things start to get worse instead of better when a width is added!

Figure 9

The first thing to notice is that the dotted line is now missing because we have set the width and therefore the background rubs out the dotted line all along that width. Also the price is now too high on the line with the longer text. Obviously we can’t use this method – so what can be done? Ahaa… what if we don’t float the left side but let it remain static instead and also do away with the width?

If we just float the right side and let the left side be static we would first need to change the HTML order so that the floated content comes first in the HTML (before the content that we want to wrap around it). (As an aside it should have been possible to leave the HTML alone in this case as floats should stay on the same line as inline content alongside it in the HTML. Floats should only move below block level content. However only Opera gets this right so we have to change the HTML around anyway.)

  1. <li><span>£11.00</span><em>Other text a bit longer</em></li>

So now we have moved the span containing the price text to the front of the HTML for each line — that should allow the text to wrap around as we hoped for. Figure 10 shows the result of these changes.

Figure 10

That’s got our layout looking closer to what we want but now the price is misplaced on the line that is wrapping. The price on the right is floated and when the text wraps to another line the right float just stays where it was. Why should it automatically move down anyway?

It seems as though at every step a new problem occurs and we are getting no nearer to achieving the effect we wanted (which is seen in Figure 7). It turns out that our current plan of action will fail to achieve the results we are looking for and we need to approach this from a different angle altogether.

Using our current design there is no way to make both the descriptive text and the floated right text both move down at the same time when the text wraps. Therefore we are going to change the design and move the right float onto the next line to start with. The descriptive text will be put in a block element (not floated) and that will ensure that the right float is always one line below the text even when the line wraps. This will give us a consistent measurement to work on and we can offset this known distance by using relative positioning once again.

The descriptive text and the right float now start on their own lines as shown in Figure 11.

Figure 11

This ensures that the price is always one line below the descriptive text. Then all we need to do is to drag the descriptive text downwards by one line height (more or less ) and it will always be in the correct position. If we control the line height and use ems for our measurements we can make sure that the whole thing always scales together. This may take a little trial and error to get things exact but it will provide us with the effect we have been striving for. We also need to push the price downwards to cover the dotted line as in our first example. This downward movement is done with relative positioning so that the lists’ bottom border doesn’t move.

This brings everything nicely into position with one slight drawback in that we have a one line height gap above the descriptive text which spaces the lists out a bit too much. This can be overcome by applying a negative top margin to the list to offset this gap. Once again we use ems so that the layout remains solid and will scale well. The exact dimensions can be tweaked depending on your situation and a little trial and error goes a long way.

There is one last issue to deal with — we would like the descriptive text to wrap before it meets the price to keep everything looking neat. I have added a 5em right padding to the p element that is now holding the em and the descriptive text. I have just guessed that 5em will be enough but we should be safe anyway because the line-height is such that the rows will not clash should the text from both sides meet.

We can now put our HTML back into a more logical order resulting in the list item looking like this.

  1. <li>
  2. <p><em>Some text a bit longer</em></p>
  3. <span>£9.99</span>
  4. </li>

There is an extra p element inserted (to satisfy Opera’s behavior as already mentioned previously) and the span once again moved after the text but also after the p element. (I have left the span as a span because we have been using it throughout the demo but it would be more semantic to use a block element now such as a p element. That would of course mean using a class to differentiate the styles or perhaps using a div instead of a p element to keep it unique. For the sake of clarity I have left it as a span for now.)

Anyway back to the example — we can see what effect the following CSS will have on our design. I have only shown the relevant parts so refer to the live demo at the end of the article for the full code.

  1. #wrap li{
  2. line-height:1.2;
  3. margin:-.9em 0 0 0;
  4. position:relative;
  5. float:left;
  6. width:100%;
  7. text-align:left;
  8. border-bottom:1px dotted #000;
  9. clear:both;
  10. }
  11. * html #wrap li{
  12. border:none;
  13. background: url(images/dotted-leader.gif) repeat-x left bottom;
  14. }
  15. #wrap li span{
  16. background:#eff2df;
  17. padding:1px 0 1px 5px;
  18. color:#000;
  19. position:relative;
  20. top:.4em;
  21. float:right;
  22. }
  23. #wrap li em{
  24. margin:0 ;
  25. position:relative;
  26. top:1.6em;
  27. padding:0 5px 0 0;
  28. background:#eff2df;
  29. }
  30. #wrap p{padding:0 5em 0 0}

The result of this can be seen in Figure 12 below.

Figure 12

Hmm…. looking good!

The above screenshot is from Firefox 2.0, I wonder what IE6 looks like?

Here we go…

Figure 13

Looks pretty good but what’s happening with the nice dotted border? They look like dashes and are a bit chunky for the design. This is an old IE problem where IE6 (and under) will display dashes instead of dotted borders when the border size is 1 pixel. There is nothing we can do about this. Or is there?

In fact we can make IE6 look much better by doing away with the bottom dotted border and using a background image instead. We can just give it to IE6 and under like so.

  1. * html #wrap li{
  2. border:none;
  3. background: url(images/dotted-leader.gif) repeat-x left bottom;
  4. }

The border is negated from the list and then a background image is used instead. The star selector hack ensures only IE6 and under gets that style rule.

That just about wraps it up and you can see the full demo in this live example. View sources to grab the full code as the CSS has been left in the head for you to get easily.

Try scaling the text size up and down and you can see that the layout is very solid and the effect is maintained. To finish we can see what the design looks like across a range of browsers.

108 Responses to “CSS – A Recipe for Success”

1 Jose B

Does anyone know where I can get the dotted-leader.gif file?
* html #wrap li{
background: url(images/dotted-leader.gif) repeat-x left bottom;
Or can anyone do a video tut?
I’m only requesting this because I followed all the instructions and I can’t get the dotted lines (……) inserted. Also, the whole menu looks messed up every time I modify the code to fit my site.
I would really appreciate all the help. THX

2 Paul OB

Hi Jose,

You can find the image by simply following the path to the image from the CSS file.

It’s here:

It’s only needed for IE6 and IE6 is pretty much dead as this article was written 5 years ago so I wouldn’t bother with it these days.

I would also use the simpler absolute position method as shown in this example:

If you need more help you will need to post a link to the page in question.

3 The Firm

I think the admin of this web page is truly working hard for his website, since here every material is
quality based data.

4 web ressource

Thank you for any other magnificent post. Where else may just anybody get that type of info in such
an ideal method of writing? I have a presentation next week, and I’m on the look for such information.

5 william

Hi I looking to use this wonderful code. I want accomplish the following.

Picture dish Name dish
Description ………….$price

can you guys help me please.

6 Paul OB

Hi William,

It’s too hard to offer help in an article post other than on issues with the article so you should post in the Sitepoint Forums where you can post code and images etc and get help more easily.

7 true

Good web site you’ve got here.. It’s difficult to find excellent writing like yours nowadays.
I truly appreciate people like you! Take care!!


Howdy just wanted to give you a quick heads up.
The words in your article seem to be running off the screen in
Firefox. I’m not sure if this is a formatting issue or something to do with web browser compatibility but I figured I’d post to let you know.
The design and style look great though! Hope you get the problem resolved soon.


mulberry sale spyder womens jacket cheap new balance 574 mulberry outlet cheap new balance 574 arcteryx outlet mulberry sale spyder womens jacket mulberry sale spyder womens jacket mulberry outlet mulberry outlet new balance 574

Popular Articles

Top 10 Commentators

Subscribe to this feed! Subscribe by Email!

Random Bits Podcast

You need to download the Flash player from Adobe

Other Sites