Layout

Flex items and min-width 0

Earlier I wrote about using flexbox to build responsive layouts. There is a behavior that can be surprising when using flexbox for layout like this. The content of a flex item in the layout can force it to not shrink appropriately. Let’s say for example you have a long URL displayed somewhere in your layout that you want to truncate. Applying overflow: hidden will not result in the item shrinking like you may expect it to. You can see an example in this codepen when resizing your browser.

This may appear as a bug, but it is actually a part of the spec for flexbox.

In the spec it says:

By default, flex items won’t shrink below their minimum content size

This means the minimum width of an item is set to the width of its content and it won’t shrink past that width. This can result in layouts appearing uneven or wrapping too early. By adding min-width: 0 to the item, it will resize at the correct ratio and still apply the overflow as desired. You can see an example here.

For more reading on this issue and others related to flexbox see this list of known bugs and the w3 spec.

Wrappable content with flexbox

Although specifying widths and proportions for layout items works, sometimes you need those widths to be based on the content rather than specific values. Sometimes we won’t know how long our content will be. This is where having a layout that responds to its content is handy.

Let’s say for example we want to create a list of items. We want the title of the item on the left, and the content for the item on the right. We would prefer if these items stay on one line, but if they don’t have enough room, the title should go above the content.

We will start with this markup

<section class="item">
  <div class="item__title">
    <h2>Item title</h2>
  </div>
  <div class="item__content">
    <p>Item content</p>
  </div>
</section>

We will now apply a little flexbox magic to get the wrapping we want. First we setup the item class as our flex container.

.item {
  display: flex;
  flex-wrap: wrap;
}

We want the title to only take up as much space as it needs to we set the flex-grow and flex-shrink properties to 0 with the shorthand flex: 0 0 auto.

.item__title {
  flex: 0 0 auto;
}

The content should take up whatever space is left so we set the flex-grow and flex-shrink properties to 1 with the shorthand flex: 1 1 auto.

.item__content {
  flex: 1 1 auto;
}

So far, this is great. If our content is too long, it will simply wrap underneath the title. No need to set arbitrary widths or breakpoints to do it either.

But wait, let’s say we want some margin in between the title and the content. The problem is, if we add margin to the left or right, it will still be there when it wraps. Luckily there is a bit of a hack we can do to get around this.

First, we add some negative margins to our item flex container.

.item {
  display: flex;
  flex-wrap: wrap;
  margin-top: -20px;
  margin-bottom: -20px;
}

We then add margins on the flex children to counteract the negative margins on the container.

.item__title {
  flex: 0 0 auto;
  margin-bottom: 20px;
  margin-left: 20px;
}

.item__content {
  flex: 1 1 auto;
  margin-bottom: 20px;
  margin-left: 20px;
}

Now our item can have 20px of margin between the title and the content, but when it wraps it will still be flush with the edge of the item.

We also may want vertical margins between two items. To do this we add another rule to our CSS.

.item + .item {
  margin-top: 20px;
}

This will add margin to the top of any item that is directly following another item in the DOM.

So there we have it. A simple way to wrap our layout based on the content within it. It allows us to use a fairly generic structure for all different lengths of content and still have it look nice.

Check out this codepen for an example of this in action.

Prioritizing layouts with the order property

In my last post I wrote about building responsive layouts with flexbox, but we can take it one step further and use the order property to help prioritize those layouts.

Let’s pretend we have a fairly typical two column layout with a sidebar on the left, and a section for the page content on the right.

We start with some simple markup.

<div class="layout">
  <aside class="layout__sidebar"></aside>
  <main class="layout__main"></main>
</div>

Then we add the following CSS to get a working responsive layout.

.layout {
  display: flex;
  flex-wrap: wrap;
}

.layout__sidebar {
  flex: 1 1 200px;
}

.layout__main {
  flex: 20 1 600px;
}

We set the main column to flex: 20 1 600px. That is shorthand for flex-grow: 20, flex-shrink: 1, and flex-basis: 600px. The flex-grow will make the main column grows 20 times more than the sidebar, which causes the sidebar to almost stay the same size. The flex-basis of 600px is so that it will wrap when it shrinks below that width.

Here is an example codepen. Try resizing your browser and notice how the sidebar goes above the main content on smaller screens. This may be fine for some designs, but let’s pretend the sidebar content isn’t that important and we don’t want it to be the highest priority on smaller screens.

This is where we can allow the order property to work its magic.

First, let’s rearrange that markup we had created to look like this.

<div class="layout">
  <main class="layout__main"></main>
  <aside class="layout__sidebar"></aside>
</div>

We change the order in the DOM so that by default the sidebar will come after the main content. We can then use the order property to change the visual order on larger screens.

Then we create a breakpoint at the width where our layout will wrap. This will be the sum of the sidebar flex-basis (200px), and the main content flex-basis (600px). This is even easier if we are using a preprocessing language like LESS or Sass because we can create variables for these values and then add them automatically for the breakpoint.

Finally, we change the order of both the sidebar and main column using the order property.

@media (min-width: 800px) {
  .layout__sidebar {
    order: 1;
  }

  .layout__main {
    order: 2;
  }
}

Now when we resize our browser the sidebar will be to the left of the main content when there is enough space, and underneath on smaller screens. You can see a working example in this codepen.

Hold up, are you sure this is a good idea?

This is a useful technique and can be a big help when it makes sense to reorganize content at different screen sizes, but it should be used with caution.

There are some drawbacks to only reorganizing content visually, and not the actual DOM elements themselves. Unexpected tab order may be confusing to users. Even though an input appears to come before another, it could actually be reversed in the tab order.

Still, in some cases that makes sense anyway. So like Spider-Man once said, “with great power, comes great responsibility”. Use it responsibly.

Update

Chris Sauvé pointed out to me that this can also be accomplished without any media queries using flex-direction: row-reverse. Check out his codepen to see it in action.

Responsive layouts with flexbox

Recently I have been playing around with some new ideas for responsive layouts. Typically one would probably set some breakpoints and use media queries throughout their CSS to change the layout at different screen sizes. This works, but you can also end up having many different breakpoints that can be difficult to manage. An alternative approach is to use flexbox with the flex-wrap property and let the component or content dictate how it fits into the layout.

Let’s assume we have this markup for a two column layout.

<div class="layout">
  <div class="layout__item"></div>
  <div class="layout__item"></div>
</div>

First, we allow the flex container to wrap its children as needed.

.layout {
  display: flex;
  flex-wrap: wrap;
}

Then we specify the preferred min-width of the children using the flex-basis property. We also allow the items to grow and shrink as needed.

.layout__item {
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 200px;
}

Or even better, we can shorten this to one line using the shorthand flex property.

.layout__item {
  flex: 1 1 200px;
}

With just these few lines of CSS we already have a working responsive layout. The key here is the flex-basis that is being set to a specific width. This allows the item to wrap when it can’t fit into that minimum size that it prefers. This is also nice because when it does wrap, it can still stretch to fill the container. Our component can now dictate the space it requires and respond correctly to fit itself into the layout. We also don’t even need any media-queries to do it. Neat.

You can see the working example on codepen.