16

I am styling a header of a webpage. I want the header to be a single line which includes a logo and some navigational links. I feel the best, most modern way to layout this header today is with CSS3's flexbox, so that is what I would like to use.

I would like for the logo to be as far left in the flex container as possible, and the remaining navigation items to be as far right as possible. This could easily be achieved by floating the elements left and right, but that is not what I would like to do. So...

How do you align child elements of a flexbox container to opposite far ends of the main axis?

There is a property for the flexbox child elements that allows you to do this on the cross axis, align-self, but it seems there is none to do this on the main axis.

The best way I have come up with to achieve this is to insert an additional, empty, element in between the logo and the navigational links to serve as a spacer. But part of the reason I am choosing to use flexbox for this header is to cohere with a responsive design and I do not know of a way to make the spacing element take up all the remaining space, regardless of the width of the viewing window.

Here is where I currently stand with the mark-up, simplified to only include the elements pertinent to this situation.

HTML

<ul>
  <!-- Should be as far left as possible -->
  <li id="main">Some Logo <span>Some tag line.</span></li>

  <!-- Should be as far right as possible -->
  <li>Home</li>
  <li>Products</li>
  <li>Price Sheet</li>
  <li>Capabilities</li>
  <li>Contact</li>
</ul>

CSS

ul {
  width:           100%; 
  display:         flex;
  flex-flow:       row nowrap;
  justify-content: flex-end;
  align-items:     center;

  padding:    0;
  margin:     0;

  list-style: none;
}

li {
  margin:     0 8px;
  padding:    8px;

  font-size:   1rem;
  text-align:  center;
  font-weight: bold;

  color:       white;
  background:  green;
  border:     solid 4px #333;
}

#main { font-size: 2rem; }
#main span { font-size: 1rem; }
JacksonHunt
  • 582
  • 6
  • 21

5 Answers5

6

From your question:

I do not know of a way to make the spacing element take up all the remaining space, regardless of the width of the viewing window.

This is exactly what the flex-grow CSS rule was designed for. If only one child element has the flex-grow attribute set, then it will take up all the remaining space in the flex container. The only markup you will need in this case is the following:

HTML:

<li id="spacer"></li>

CSS:

#spacer {
    visibility: hidden;
    flex-grow: 1;    
}

Full Live Demo:

ul {
  width:           100%; 
  display:         flex;
  flex-flow:       row nowrap;
  justify-content: flex-end;
  align-items:     center;

  padding:    0;
  margin:     0;

  list-style: none;
}

li {
  margin:     0 8px;
  padding:    8px;

  font-size:   1rem;
  text-align:  center;
  font-weight: bold;

  color:       white;
  background:  green;
  border:     solid 4px #333;
}

#main { font-size: 2rem; }
#main span { font-size: 1rem; }

#spacer {
    visibility: hidden;
    flex-grow: 1;    
}
<ul>
  <!-- Should be as far left as possible -->
  <li id="main">Some Logo <span>Some tag line.</span></li>

  <!-- Spacer element -->
  <li id="spacer"></li>
    
  <!-- Should be as far right as possible -->
  <li>Home</li>
  <li>Products</li>
  <li>Price Sheet</li>
  <li>Capabilities</li>
  <li>Contact</li>
</ul>

JSFiddle Version: https://jsfiddle.net/7oaahkk1/

Maximillian Laumeister
  • 19,884
  • 8
  • 59
  • 78
  • Very well done! I discovered this just before coming here to see your answer. Thank you and cheers. @DavidDomain I see you were onto this too – I even like that your method does not require a spacer. However, I feel Maximillian 's answer is better practice, as the size of the important elements (those with content) are retained regardless of window size. – JacksonHunt Sep 17 '15 at 19:55
  • That it is your answer aside, would you agree this method is better practice? – JacksonHunt Sep 17 '15 at 20:00
  • @JacksonHunt Both answers are good practice in my opinion, they just do slightly different things. David's answer grows the leftmost element to fill the extra space, whereas mine uses a spacer to fill the extra space. So it really depends on whether you want your leftmost element to grow in size to fill the space. If not, use my solution, if so, use his. – Maximillian Laumeister Sep 17 '15 at 20:03
3

I think the only flexibility that is needed, at least on large screens, should go on the first flex-item in the list. The one you want to place your logo at.

By setting this items flex-grow rule to 1 and the text-align to left it will stay on the left side, growing in size, making sure all other items stay on the right side. Since the logo may have a greater height value than all the other items it would make sense to change the align-items rule to baseline, making sure all items are horizontally aligned.

Furthermore i have added a few media queries to change the flex settings accordingly.

html {
    box-sizing: border-box;
}

*, *:before, *:after {
    box-sizing: border-box;
}

body {
    margin: 0;
}

ul {
    width:           100%; 
    display:         flex;
    flex-flow:       column nowrap;
    justify-content: center;
    align-items:     stretch;

    padding:         0;
    margin:          0;

    list-style:      none;
}

li {
    margin:     0;
    padding:    8px;

    font-size:   1rem;
    text-align:  center;
    font-weight: bold;

    color:       white;
    background:  green;
    border:     solid 4px #333;

}

li:first-child {
    font-family: sans;
    font-size: 2em;
    text-align: center;
}

li:first-child span {
    font-size: initial;
}

@media (min-width: 34em) {
    ul {
        flex-flow: row wrap;
        justify-content: space-between;
        align-items:     flex-start;
    }

    li {
        flex: 1;
    }

    #main {
        flex: 0 0 100vw;
    }
}

@media (min-width: 48em) {
    ul {
        justify-content: flex-end;
        align-items:     baseline;
    }

    li {
        flex: none;
    }

    #main {
        flex: 1 0 auto;
        text-align: left;
    }
}
<ul>
  <!-- Should be as far left as possible -->
  <li id="main">Some Logo <span>Some tag line.</span></li>
  <!-- Should be as far right as possible -->
  <li>Home</li>
  <li>Products</li>
  <li>Price Sheet</li>
  <li>Capabilities</li>
  <li>Contact</li>
</ul>
DavidDomain
  • 14,976
  • 4
  • 42
  • 50
  • I think this is a good answer and one that should be referenced by anyone looking to do this in the future. However, can I suggest that you remove the media queries and focus on answering the question directly? I feel this would be more beneficial. It also seems that the responsiveness fails at an intermediate window size and the flex children begin to wrap. – JacksonHunt Sep 17 '15 at 20:10
  • @JacksonHunt - Thank you for your feedback, appreciate it. Though i do not fully agree. I wouldn't consider adding an extra empty list element as better practice. The opposite is true. This will make pseudo selectors like `:nth-child` or `nth-of-type` with `odd` or `even` values useless. Same goes for JS iteration. – DavidDomain Sep 18 '15 at 01:16
1

Essentially you need a row container with two child columns

  • container is a flexible div
  • column one is a flexed div with the logo
  • column two is a flexible ul with flexed li's

AND 'justify-content: space-between' to move the columns to the far ends

Check my snippet (full page)!

.container,
.menu {
  display: flex;
}
.container {
  justify-content: space-between;
  width: 100%;
}
.menu {
  padding: 0;
  margin: 0;
  list-style: none;
}
li,
.logo {
  margin: 0 8px;
  padding: 8px;
  font-size: 1rem;
  text-align: center;
  font-weight: bold;
  color: white;
  background: green;
  border: solid 4px #333;
}
#main {
  font-size: 2rem;
}
#main span {
  font-size: 1rem;
}
<div class="container">
  <div id="main" class="logo">Some Logo <span>Some tag line.</span>
  </div>
  <ul class="menu">
    <li>Home</li>
    <li>Products</li>
    <li>Price Sheet</li>
    <li>Capabilities</li>
    <li>Contact</li>
  </ul>
</div>
Rene van der Lende
  • 4,992
  • 2
  • 14
  • 25
0

JSfiddle Demo

  1. Wrap a ul over the rest of the list items and use nested flex container. This is to provide flexbox to act on two elements.
  2. Use justify-content: space-between on the main flexbox parent to equally space the two elements.

.parent-menu {
  width: 100%;
  display: flex;
  flex-flow: row nowrap;
  justify-content: space-between;
  /* Modify */
  align-items: center;
  padding: 0;
  margin: 0;
  list-style: none;
}
li {
  margin: 0 8px;
  padding: 8px;
  font-size: 1rem;
  text-align: center;
  font-weight: bold;
  color: white;
  background: green;
  border: solid 4px #333;
}
#main {
  font-size: 2rem;
}
#main span {
  font-size: 1rem;
}
.right-menu {
  display: flex;
  /* Add */
}
<ul class="parent-menu">
  <!-- Should be as far left as possible -->
  <li id="main">Some Logo <span>Some tag line.</span>
  </li>
  <ul class="right-menu">
    <!-- Should be as far right as possible -->
    <li>Home</li>
    <li>Products</li>
    <li>Price Sheet</li>
    <li>Capabilities</li>
    <li>Contact</li>
  </ul>
</ul>
m4n0
  • 29,823
  • 27
  • 76
  • 89
  • 1
    The problem with this solution is that the mark-up is invalid. [You can't make `nav` (or `div` or `span`, for that matter) a child of a `ul`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). – Michael Benjamin Sep 13 '15 at 02:15
  • @Michael_B Thanks for pointing that out :) I think nested ul will work in that case. – m4n0 Sep 13 '15 at 08:25
0

One posibility is to set a right margin on the first element

ul {
  width:           100%; 
  display:         flex;
  flex-flow:       row nowrap;
  justify-content: flex-end;
  align-items:     center;

  padding:    0;
  margin:     0;

  list-style: none;
}

li {
  margin:     0 8px;
  padding:    8px;

  font-size:   1rem;
  text-align:  center;
  font-weight: bold;

  color:       white;
  background:  green;
  border:     solid 4px #333;
}

#main { 
  font-size: 2rem; 
  margin-right: auto;  /* create a right margin as needed */
}  
#main span { font-size: 1rem; }

#spacer {
    visibility: hidden;
    flex-grow: 1;    
}
<ul>
  <!-- Should be as far left as possible -->
  <li id="main">Some Logo <span>Some tag line.</span></li>
  <!-- Should be as far right as possible -->
  <li>Home</li>
  <li>Products</li>
  <li>Price Sheet</li>
  <li>Capabilities</li>
  <li>Contact</li>
</ul>
vals
  • 61,425
  • 11
  • 89
  • 138