31

I’m looking for a way to align multiple items next to each other, having them wrap once they fill up the line, and the last item take up the remaining width of the last line.

I have found multiple questions on a similar topic before, with solutions generally making the items use float: left to align them on a line, and overflow: hidden on the last element which makes it take up the remaining space automatically. Those solutions work fine in a single line, but they stop working once there are enough items before the last element, that they wrap into multiple lines. Then, the “last” element is still rendered in the first row (if there is enough space), making it no longer be the last element.

However, I want the last element to stay the last element at all times, to have it in the last row—whatever row that is—and automatically take up the remaining space there.

This is pretty simple with a wrapping flexbox, since you just need to put flex: 0 auto on the former items to make them take up whatever space they need (without taking more), and flex: 1 on the last element to make it take up the remainder. However, I need to support Internet Explorer 9, so flexbox is unfortunately out of the question.

This is how the situation looks like. When running the snippet, you can use the “Toggle flex box” button to toggle flexbox mode which shows the way it should look like.

* { box-sizing: border-box; }
.container {
  width: 300px;
  background: snow;
  padding: 5px;
  overflow: auto;
}
.element {
  float: left;
  margin: 2px 5px 2px 0;
  background: lavender;
}
.last {
  overflow: hidden;
}
.last > input {
  width: 100%;
}

/* Working solution with flex box */
.flex .container {
  display: flex;
  flex-wrap: wrap;
}
.flex .element {
  flex: 0 auto;
}
.flex .last {
  flex: 1;
}
<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last"><input type="text" /></div>
</div>

<button onclick="this.parentNode.classList.toggle('flex')">Toggle flex box</button>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
poke
  • 369,085
  • 72
  • 557
  • 602
  • Nice question, I didn't try to work with css flex before so I have no idea to work around with css in this case. Did you consider doing it manually by calculating the remaining width and fix the last item with that width by using js? – Raphaël VO Jan 08 '16 at 16:09
  • 1
    The flexbox solution is just there to show how it should look like (and how you could solve this easily using modern browsers), but since I’m looking for a solution without flexbox, you can simply ignore that solution. And yeah, solving it with JavaScript is my fallback workaround in case this proves to be unsolvable. – poke Jan 08 '16 at 16:13
  • Well, as you still need to support IE9, so JS may be a better option. But I will try to work around this case, I remembered I saw it somewhere and fixed it with css only – Raphaël VO Jan 08 '16 at 16:18
  • in your case, I found you didn't treat it naturally, as the item with ``class="last"`` is not the last one so force it to fit the first line may be tricky – Raphaël VO Jan 08 '16 at 16:21
  • @ThoVo I’m not sure what you mean? I want the element `.last` (which can be placed anywhere in the code if that helps) to fill up the very last line. It rendering in the first line despite there being multiple lines is exactly what I don’t want (but what’s happening with this solution). – poke Jan 08 '16 at 16:27
  • ok, in natual way, the last item should be the last element in your DOM, in this case, you try to force some elements not is the last to be at last of a line. But forget it, I think I come up with an idea to solve your problem. You may want to see how Foundation works with their grids to find a way to apply in your case, they have class ``.expanded`` I think you can use. Check this link below: http://foundation.zurb.com/sites/docs/grid.html – Raphaël VO Jan 08 '16 at 16:36
  • @ThoVo In my example code `.last` is the last element in the DOM (for each container), so I’m not sure what you think is wrong there. But then again, `.last` is just a name, you can name it whatever if you want. As for that grid system, that won’t work since the grid cells are of a fixed (relative percentage) width, but the `.element` objects should have a dynamic width. And as per that documentation, `.expanded` only makes the row fluid (removes the 1200px width limit). – poke Jan 08 '16 at 16:43
  • @CodeiSir As mentioned in a comment above and in the bounty description, I’m looking for a solution without JavaScript. – poke Jan 11 '16 at 15:22
  • @CodeiSir As I said, JavaScript is my fallback solution, but I am interested in a solution that can do without. I have no interest in an answer that solves this with JavaScript as I’m doing that already. – poke Jan 11 '16 at 15:47
  • @CodeiSir Because I’m only interested in a HTML/CSS-only solution? That really shouldn’t be difficult to understand. – poke Jan 11 '16 at 15:51

2 Answers2

13

Here is the solution:

  • Add a <div style="clear:left;margin-top:22px"></div> before the .last element*
  • Add margin-top: -22px; to .last where the amount is about the same as the line-height
  • If you dont want it to get too small add min-width to .last and it will work as you'd expect
    Also min-width: 1px; is requiered in the class to avoid it getting 0 in special cases.

Tested and working in IE8+, Edge, Chrome, Opera, Firefox

* { box-sizing: border-box; }
.container {
  width: 300px;
  background: snow;
  padding: 5px;
  overflow: auto;
}
.element {
  float: left;
  margin: 2px 5px 2px 0;
  background: lavender;
}
.last {
  overflow: hidden;
  margin-top: -22px;
  min-width: 1px;
}
.last > input {
  width: 100%;
}
.container .unwrap {
  clear: left;
  margin-top: 22px
}
<div class="container">
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz boo</div>
  <div class="unwrap"></div>
  <div class="last" style="min-width: 100px">
    <input type="text" placeholder="min-width 100px" />
  </div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo baaaaaaaaaaaaaaaaaaaaaaar</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

* margin-top:22px only to avoid the input to slip over in case there are no elements in the container. It should be the absolute value of the negative margin of the input

CoderPi
  • 12,985
  • 4
  • 34
  • 62
4

The following solution isn't perfect, it's using the position hacks. If you disable overflow: hidden; you'll see each <input> actually has the same width as the container. But with some CSS visual tweaks, it looks good and may work ok.

Issue 1: if you type in more characters than it can visually fit, the whole view shifts to the left.

Workaround 1: set a maxlength value to the <input> might help.

jsfiddle

* {
  box-sizing: border-box;
}
.container {
  width: 300px;
  background: snow;
  padding: 5px;
  position: relative;
  overflow: hidden; /*hide the overflow*/
}
span:after {
  content: "\00A0" /*just for one no-break space*/
}
input {
  width: 100%;
  position: absolute; /*without any top/right/bottom/left value*/
  border: 2px solid grey;
  border-width: 0 0 2px 0;
}
input:focus {
  outline: 0;
}
<div class="container">
  <span>FooFoo barFoo bar baz</span>
  <input type="text" />
</div>

<div class="container">
  <span>FooFoo barFoo bar bazFooFoo barFoo bar baz bla</span>
  <input type="text" />
</div>
Stickers
  • 75,527
  • 23
  • 147
  • 186
  • This unfortunately breaks if you are typing in more character than can visually fit into the displayed part of the text input. Once you reach the end, the whole view scrolls to the right as you type in more. – poke Jan 08 '16 at 18:18
  • You're right. It's not *perfect*, but that is the best way I have tried with plain css with your restrictions. As a workaround I wonder set a `maxlength` could be helpful, well it also depends on your requirements. – Stickers Jan 08 '16 at 18:42
  • I'd love to see a better answer too, do you consider to start a bounty? I'll probably do it if don't have the plan. – Stickers Jan 10 '16 at 16:59
  • I’ll probably start one if I don’t get any more responses in the next few days. Having tried a few more things myself, I fear that there really isn’t any non-JS solution to this :/ – poke Jan 10 '16 at 17:02