2

I ran into an interesting CSS problem today, and I have been wracking my brain trying to solve it.

This is similar to the trivial problem of "a row of three elements, with a left, a right, and center," which can be solved easily with flexbox — but it has a couple of caveats that make it (I think) an impossible layout without JavaScript.


The desired goal

Consider a row-like container element and three children, "left", "right", and "center". The children may be of varying widths, but they are all the same height.

"Center" should try to stay centered relative to its container — but the three sibling elements must not overlap, and may push outside the container if necessary.

The markup, then, might look something like this:

<div class="container">
    <div class="left">I'm the left content.</div>
    <div class="center">I'm the center content. I'm longer than the others.</div>
    <div class="right">Right.</div>
</div>

The CSS is where the challenge is.


Examples of what should happen

For wide containers, "center" is centered relative to the container (i.e., its siblings' widths do not matter), as in the image below; notice that midpoint of the "center" element matches the midpoint of the container, and that the left and right "leftover" spaces are not equal:

Wide

For narrower containers, "center" abuts the widest sibling, but it does not overlap. The remaining space is distributed only between the narrow sibling and the "center" sibling. Notice also that the container's midpoint, indicated by the caret, is no longer the same as "center's" midpoint:

Narrower

Finally, as the container continues to shrink, there's no other option but to have all three elements lined up in a row, overflowing the parent:

Narrowest


My attempts to solve this

Surprisingly, I haven't found a good way to implement this in pure CSS.

You'd think flexbox would be the winner, but you can't really get flexbox to do it right: The space-between property distributes the space uniformly between the elements, so the center element doesn't actually end up centered. The flex-grow/shrink/basis properties aren't especially useful for this either, since they're responsible for controlling the size of the child elements, not for controlling the size of the space between them.

Using position:absolute can solve it as long as the container is wide enough, but when the container shrinks, you end up with overlap.

(And float layouts can't get within a mile of getting this right.)

I could combine the best two solutions above, and switch between them with a @media query — if all of the widths were known in advance. But they aren't, and the sizes may vary widely.

In short, there's no pure-HTML-and-CSS solution to this problem that I know of.


Conclusion, and a JSFiddle to experiment with

I created a JSFiddle that shows both the desired goal and a few non-solutions. Feel free to fork it and experiment. You can simulate the container resizing by grabbing the bar to the left of the content and dragging it. You are allowed to rearrange/restructure the HTML and CSS, if rewriting it gets you closer to a working answer.

https://jsfiddle.net/seanofw/35qmdnd6

So does anyone have a solution to this that doesn't involve using JavaScript to intelligently distribute the space between the elements?

Sean Werkema
  • 5,810
  • 2
  • 38
  • 42
  • See Solution #2 in section ***Center a flex item when adjacent items vary in size*** in this answer: http://stackoverflow.com/a/33856609/3597276 – Michael Benjamin Feb 25 '17 at 14:17
  • 1
    @Michael_B This was a clear dupe, and very good with the comment where to look, upvoted on your question and answer...next time simply close it :) – Asons Feb 25 '17 at 17:47
  • Sometimes I wait for confirmation from the OP before closing the question as a dupe. Also, this a very well-crafted question. (If the code were included it would have been perfect ;-) Shame to close it. – Michael Benjamin Feb 25 '17 at 17:56
  • 1
    @Michael_B Agree .. since I posted an answer, you decide if it should be reopened – Asons Feb 25 '17 at 20:42
  • I don't agree that it's a duplicate: The fact that a partial answer to this can be found several miles down an unrelated answer to a general question about the CSS spec really doesn't make this "already answered." I spent quite some time searching SO before posting this —I don't write mile-long detailed questions without a good reason! In all my searching, I never found that other question, so anyone else trying to solve the same layout problem likely isn't going to end up there either, and definitely isn't going to find a solution without LGSon's answer here. – Sean Werkema Feb 26 '17 at 14:52
  • And because I think it's still a question that has some value, I added a code snippet that illustrates the problem better, per Michael_B's suggestion. – Sean Werkema Feb 26 '17 at 15:00
  • @Michael_B Based on the comments I decided to reopen it, and updated my answer with a reference to link the 2 posts – Asons Feb 26 '17 at 15:16

1 Answers1

2

With flexbox you should be able to solve that, by giving the left/right elements flex: 1 and the right text-align: right.

The main trick is flex: 1, which will make them share available space equally.

For more versions, see this brilliant question/answer, flexbox-justify-items-and-justify-self-properties

Fiddle snippet

Stack snippet

body {
  font: 14px Arial;
}
.container {
  display: flex;
  border: 1px solid #00F;
}
.container > div > span {
  display: inline-block;
  background: #36F;
  white-space: nowrap;
  padding: 2px 4px;
  color: #FFF;
}
.container > .center > span {
  background: #696;
}

.container .left,
.container .right {
  flex: 1;
}
.container .right {
  text-align: right;
}

.center-mark {
  text-align: center;
  font-size: 80%;
}
.note {
  text-align: center;
  color: #666;
  font-style: italic;
  font-size: 90%;
}
<div class="container">
  <div class="left">
    <span>
      I'm the left content.
    </span>
  </div>
  <div class="center">
  <span>I'm the center content. I'm longer than the others.</span>
  </div>
  <div class="right">
    <span>
      Right.
    </span>
  </div>
</div>
<div class="center-mark">^</div>
<div class="note">(centered marker/text)</div>
Community
  • 1
  • 1
Asons
  • 84,923
  • 12
  • 110
  • 165
  • This is *almost* a full solution: The child items got resized by it, so to get the behavior I'm searching for, you'd have to modify the DOM and add additional non-resizing child elements inside each of left/right and center that are effectively the *actual* elements. But it does lead to a pure-CSS solution, so upvote and accepted. – Sean Werkema Feb 26 '17 at 14:40
  • @SeanWerkema Thanks .. and I reopened your question – Asons Feb 26 '17 at 15:14
  • You're welcome! Thank you for a working solution! It may be worthwhile to edit your answer to include the nested non-resizing elements, just so that someone looking for it can see what it the whole answer looks like. A lot of people copy-paste stuff from the intarwebz without really thinking about it, and I'll bet some people would copy this as given and then ask, "How come it dunnt work?" – Sean Werkema Feb 26 '17 at 16:32
  • 1
    @SeanWerkema Updated – Asons Feb 26 '17 at 17:37