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:
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:
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:
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.
So does anyone have a solution to this that doesn't involve using JavaScript to intelligently distribute the space between the elements?
With
flexbox
you should be able to solve that, by giving theleft
/right
elementsflex: 1
and the righttext-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