Flexbox Nav Bar with Fixed, Variable, and Take-Up-The-Rest Elements

Izak Filmalter wrote to me:

On the site I am working on there is a navigation bar with a few elements in it: a search input, the user's full name, and some icons. The icons are of fixed with, the user's name varies, and the search bar should take up the rest of the usable space.

No matter what I try, I can not get the search bar to use all the available space and no more when the user name length is dynamic.

I already sent Izak a response, but I'll share that solution here. Flexbox! I'm sure it comes as no surprise that flexbox is a wonderful layout tool. I have a guide to flexbox here that makes for a pretty good reference.

The good news here is that this is a pretty easy thing to do with flexbox layout. We'll get more detailed, but these few lines of code basically get it done:

.bar {
  display: flex;
}
.search {
  flex: 1;
} 

Step by Step

Let's assume markup like this. No surprises here really. I just wrapped the search input in a <div> mostly because I'm weirded out by an input becoming a flex item, but you'd probably want a wrapping element (like a <form>) anyway, because you'd probably have a <label> and whatnot.

<div class="bar">
  <div class="icon icon-1"></div>
  <div class="icon icon-2"></div>
  <div class="icon icon-3"></div>
  <div class="username">
    Emily Blunt
  </div>
  <div class="search">
    <input type="search" placeholder="search..." />
  </div>
</div>
Without any CSS

To get them aligned horizontally and centered:

.bar {
  display: flex;
  align-items: center;
}

Let's make sure the bar is as wide as the browser window. We can set 100% width easily enough. But whenever I do that, I'm reminded we should be rocking box-sizing: border-box; - otherwise any padding or border on that container and it would be wider than 100% and that's the worst.

*, *:before, *:after {
  box-sizing: inherit;
}
html {
  box-sizing: border-box;
}
.bar {
  display: flex;
  align-items: center;
  width: 100%;
  background: #eee;
  padding: 20px;
}

For the final trick:

.search {
  /* take up the rest of the remaining space */
  flex: 1;
}
.search input {
  width: 100%;
}

It'll work like this:

Now that we're in flexbox-land, we can even flop the order of things around all we want and get the same good spacing action. The order property is 0 by default, so any positive value will put that flex item at the end or negative value at the beginning. And then in order, just like z-index.

.bar-2 .username {
  order: 2;
}
.bar-2 .icon-3 {
  order: 3;
}

.bar-3 .search {
  order: -1;
}
.bar-3 .username {
  order: 1;
}

Bonus RWD!

Flexbox is super friendly toward responsive design, because we can swap around ordering and sizing, and even wrapping, really easily. Let's make our demo wrap, and make the search and username full-width:

@media (max-width: 650px) {
  .bar {
    flex-wrap: wrap;
  }
  .icon {
    order: 0 !important;
  }
  .username {
    order: 1 !important;
    width: 100%;
    margin: 15px;
  }
  .search {
    order: 2 !important;
    width: 100%;
  }
}

Demo

Here we go:

See the Pen Flexbox reordering by Chris Coyier (@chriscoyier) on CodePen.

Browser Support

You can't mention flexbox these days without some rabble-rabble browser support. As always, you can consult Can I Use. IE 9 is the big limiter left. If you need to support that, you'll have to find another way. Otherwise, between old / new / tweener syntaxes, you'll be fine. We aren't using anything weird here.

The trick is just to use Autoprefixer which handles flexbox wonderfully. When using that, the flexbox properties/values in this demo become:

.bar {
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
}

.search {
  -webkit-flex: 1;
  -ms-flex: 1;
  flex: 1;
}
.bar-2 .username {
  -webkit-order: 2;
  -ms-flex-order: 2;
  order: 2;
}

But I need a better fallback!

One possibility is to use Modernizr to detect flexbox support. It'll put a class name on the html element, like:

<html class="no-flexbox">

You can use that to use a different layout technique:

.no-flexbox .bar {
  display: table;
  border-spacing: 15px;
  padding: 0;
}
.no-flexbox .bar > * {
  display: table-cell;
  vertical-align: middle;
  white-space: nowrap;
}
.no-flexbox .username {
  width: 1px;
}

Works pretty good:

It's not the only possible way. You could mess with inline-block or float and measuring things with JavaScript. Or you could use an actual table. The web, yo, there is always a way.