How to space padding evenly among menu links?

I have a horizontal menu with links like this:

<ul>
    <li>
        <a href="/"><span>Main</span></a>
    </li>
    <li>
        <a href="/"><span>Me</span></a>
    </li>
    <li>
        <a href="/"><span>Offer</span></a>
    </li>
    <li>
        <a href="/"><span>Pricing and Promotions</span></a>
    </li>
    <li>
        <a href="/"><span>Classes</span></a>
    </li>
    <li>
        <a href="/"><span>Contact</span></a>
    </li>
</ul>

I want the left and right padding (the green area) of each link to be equal regardless of how much text I put into the links. We can assume the width of the whole menu (between the yellow borders) is fixed, either in pixels or in ems, but the client can change the names of the menu links. If the text changes then the width of the links also changes and I would like the horizontal paddings to be always evenly distributed with css alone. How can this be done?

So far I’ve tried applying display: table-cell to the <li> elements and while the whole menu was filling the whole width the browsers didn’t keep equal paddings - the paddings of links with more text were much wider than those with little text. As a temporary solution I set fixed width to some of the menu items but this is far from how it should be.

Hi there Lemon,

if you would be willing to forsake the border,
then this might suit you requirements…

<!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>untitled document</title>

<link rel="stylesheet" href="screen.css" media="screen">

<style media="screen">
body {
    background-color: #f0f0f0;
    font: 1em/150% verdana, arial, helvetica, sans-serif;
 }
#menu {
    padding: 0.5em 0;
    border-right: 0.5em solid #ffcd74;
    border-left: 0.5em solid #ffcd74;
 }
ul {
    display: flex;
    justify-content: space-evenly;
    flex-wrap: wrap;
    padding: 0;
    margin: 0;
    list-style: none;
    background-color: #536453;
    text-align: center;
 }
ul a {
    display: block;
    padding: 0.25em 1em;
    color: #fff;
    text-decoration: none;
    text-align: center;
 }

</style>

</head>
<body> 

<div id="menu">
 <ul>
  <li><a href="#">Main</a></li>
  <li><a href="#">Me</a></li>
  <li><a href="#">Offer</a></li>
  <li><a href="#">Pricing and Promotions</a></li>
  <li><a href="#">Classes</a></li>
  <li><a href="#">Contact</a></li>
 </ul>
</div>

</body>
</html>

coothead

Does this do what you want ?

2 Likes

The border is a requirements here… Actually, I got as far with flex as you :slight_smile:

Yes, brilliant! I haven’t yet fully grasped all the intricacies of flex and I wasn’t sure if flex can do it but it’s great to know that it can, thanks a lot.

BTW, why did you wrap the link texts in spans? To me they appear to be redundant. Edit: sorry, the spans appear to be submitted by me, so forget it :smiley:

Flex can do a lot of interesting things.
This is a reference that I found very useful in learning to use flex:-

Hi there Lemon_Juice,

note that Paul’s code requires “flex-wrap: wrap;”
to be added to the “ul” rules, so that the menu is
fully responsive. :winky:

coothead

I studied it, too, but the crucial flex-basis: auto was not described clearly enough for me there so I missed this usage.

I played with flex-wrap: wrap some time ago but the main problem to me was that it looked weird when 5 links were in one line and only 1 link in the second line taking up all width - you can see this when you add it to the codepen and gradually shrink the browser. What I would expect here is to distribute the links more or less evenly across all the lines so there are equal number of links in each line, as much as possible. Or, the distribution across the lines could be based on the width of the links not the quantity alone so that the whole menu looks balanced. Is flex capable of doing this?

Yes the three properties found in the flex shorthand property: flex-grow, flex-shrink and flex-basis, can be confusing to understand at first.
flex-basis: auto is a default value which will leave items free to size themselves based on content.
The “magic” property in Paul’s example which does what you asked for is actually flex-grow: 1. This makes the items expand to fill any spare space in the container. The value is relative, so all items having equal values, they all take an equal amount of the free space, giving the effect you want.
flex-basis when given a value (in conjunction with grow and/or shrink), I would describe as giving items an implied size, as in if there is grow or shrink, it’s not rigid, there is some flexibility to grow to fill free space, or shrink to fit a tight space. Think of it like a helical spring’s length at rest. When used with wrap it can determine at what point the items start to wrap. Without any grow or shrink, it’s like setting a width.

This is one annoying limitation of flex. The wrap behaves in the same way as words or inline elements. If there is not quite enough room for all items, just the last one gets popped off to the next line, then the next. I’m not aware of a way to automatically make the distribution of items on lines more even.
You would have to manually set a media query to change the rules at a certain width to equalise the widths.

4 Likes

Thanks for this code sample, this workaround with media queries looks good enough for most cases.

There’s just one more thing bothering me - is it possible to align the text vertically within the links when the layout switches to 2 or 3 columns? I know in flex can be used to align items vertically but in this case this should apply to the text content of the <a> tags. Probably make use of the wrapper spans and use flex within flex?

That’s right, nested flexes will do it.
See the updated fork:-

2 Likes

This is getting more and more complex :slight_smile: For the narrow versions I can see three display: flex;, which indicates there are actually three levels of nested flexes. But that is a great example, thanks!

You can end up with a chain of nested flexes like this. It just reflects the nesting of the elements. The ul had flex from the start to arrange the lis. Then the lis have it to make the a fill them, then the a has flex to vertically align it’s content, though the spans are still not needed.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.