a flex-wrap detector story ...
Doing responsive layouts is where a lot of the challenge of CSS is nowadays, since there are finally many good ways to center things 😉. We can do a lot with @media queries, and the recently widely available, even better @container queries.
Queries have a major limitation though - they require you to specify specific breakpoint pixel values up front. This usually works, but can become very limiting with dynamic content like translations or variable numbers of buttons. Before you know it, things start to collapse, scrollbars appear, or content might even overflow and disappear from view completely 🤦♂️ if you're not careful.
Best approach to responsive layout has always been to be "fluid" 🌊. To avoid specific pixel break-point values and adapt the layout to what the content is - let it "flow". This is challenging to execute well though. It can be done by utilizing things like flexbox wrap, viewport units, min/max/clamp functions and percentages.
In practice it's far from straightforward or intuitive in a lot of cases, and some things are just not possible to do in pure CSS 😢.
Sometimes you just wish you can tell the browser 🤷 in css:
"When it no longer fits - do this styling instead"
There is a way to do this (kind of) this in pure css - flex-wrap
. It is very limited though - flex items that don't fit into the container can go into the next line. And that's basically it 😒
What if we want to say "if this flex container would wrap, do this instead" and "this" could be anything, not just move the item into the next line.
Is this even possible ?
Well not in CSS for various reasons, but in general using some javascript - it now is. Introducing...
Fluid Flexbox - flex wrap on steroids!
and it's general purpose sibling
<flex-wrap-detector>
html custom element
<FluidFlexbox>
react component and <flex-wrap-detector>
html custom element allow you to do exactly that! - specifying alternative styling and content of a flex container and it's children when the flex items don't fit into a single line and would wrap.
They have a pretty straightforward API and allow building fluid layouts from very simple to extremely complex, fully responsive, and based on content not magic pixel values.
Let's see some examples:
Starting with a simple one: when it no longer fits - change flex direction. It's literally a one liner.
<FluidFlexbox className="gap-2" wrappedClass="flex-col">
<Button>First</Button>
<Button>Second</Button>
<Button>Third</Button>
</FluidFlexbox>
This is a “level 0” responsive technique, but it's always based on a media query and assumes that if the screen / container is wider than the breakpoint all buttons will neatly fit into one line. In practice though, they may not 🤷. If we can’t estimate the width up front we have to either break early or have allow wrapping
FluidFlexbox fixes all that - the layout changes at exactly the point when the buttons stop fitting.
The html custom element usage is also quite straightforward:
<script defer src="https://unpkg.com/fluid-flexbox@latest/dist/web/flex-wrap-detector.umd.js"></script>
<flex-wrap-detector wrapped-class="flex-col">
<div class="flex gap-2">
<div class="button-example">First</div>
<div class="button-example">Second</div>
<div class="button-example">Third</div>
</div>
</flex-wrap-detector>
That's the pure CSS usage, it means that you can apply any styling to the flex container and children when they no longer fit. It's really powerful, you can make it look completely different.
It works kind of like a ::collapsed
pseudo-class would 🤓.
It doesn't stop there though. You can also change the underlying HTML content. The React component makes it really easy (using a render prop):
<FluidFlexbox className="gap-2">
{(isWrapped) => (
<>
<Button>Remove</Button>
<Button>Extra</Button>
{!isWrapped && <Button>Button</Button>}
</>
)}
</FluidFlexbox>
It's a little more explicit in raw html:
<flex-wrap-detector>
<div class="flex gap-2">
<div class="button-example">Remove</div>
<div class="button-example">Extra</div>
<div class="button-example">Button</div>
</div>
<div slot="wrapped-content" class="flex-col gap-2">
<div class="button-example">Remove</div>
<div class="button-example">Extra</div>
</div>
</flex-wrap-detector>
You need to provide alternative html content into slot="wrapped-content"
(there is another way of doing that as well)
In this example, when space gets tight, we simply hide the last button instead of letting it wrap to the next line.
This means you can do anything - even render something completely different 🎉 !
And yes, this fully dynamic. If the content changes or some styles affect either the container or the children it will still detect whether it wraps or not, and behave accordingly.
You can really do a lot - even nest things to achieve cool behaviours like that:
Checkout examples on Stackblitz and play around live.
As a final showcase let's try this "holy responsive grail" 🏆
(Okay it's there is no such thing 😉 , but I think it would be a neat responsive behaviour):
6 menu buttons first collapse into 2 lines than 3 than into a hamburger menu - no breakpoints, magic pixel values or anything like that, fully fluid responsive toolbar ✨
Code for react-version and pure html version.
The How:
You might be asking yourself by now : "There must some black magic going on here right? 🪄 Surely there have to be some caveats, this is too good to be true ? 🤔 ".
Yeah, fair point otherwise we'd probably have a ::flex-wrapped
pseudo-class in css by now.
There are indeed some caveats, but not something to worry about in almost all use cases, I believe a fair price to pay for the power and flexibility it allows. 💪
Here's how it works:
The technique
So how would one go about building such a detector? One way would be to do all measurements of the container and items and decide when the items would wrap - essentially building a little layout engine. But it doesn't really make sense since there is a state of the art engine available - the browser!
The only thing to do is to trick the browser to do this for us without affecting the layout. And thankfully we can leverage, a slightly underused if you ask me, css property - visibility: hidden
. It will allow rendering two hidden copies of the original flex container, have them look and behave exactly as the original, but not mess with the real visible ui.
How does it know it wrapped ?
The two copies are almost identical to the original content and basically only differ by one css property flex-wrap
. One hidden flex container allows wrapping, the other does not.
Then resize observers are used to trigger the check - which is offset-top
of each of the flex items - in the "wrapping copy". If any of it is different than the non wrapping one we know the content would wrap.
Hopefully 🤞 the gif below can help to visualize what is going on. It's adds a 3d perspective to see how the original content "gray", "non-wrapping copy" - "red" and "wrapping" copy - "blue" behave:
Why two copies ?
To detect when it no longer fits is easy enough. The original content that is not wrapping can be compared to a copy that is allowed to wrap. The problem is, it also needs to detect when it would fit again 🤦♂️ and react accordingly. So because the alternative content is now on screen the orgiginal content needs to be kept in the dom in case the width or other circumstances change and it can fit again. And we still need both the wrapping and non-wrapping versions of the original content.
All of this is of course non trivial - there is much more details like positioning, stacking, infinite loops etc.. 😮💨, but I will not bore you with even more details 😇
Using the technique in practice
The first implementation of the technique is a React component. This is not only because it's the most popular Frontend framework, but also because React makes it very easy to make it dynamic and to have a reasonable API. React's rendering abstraction makes it trivial to render the copies to run the checks on. And the virtual DOM takes care of making it all dynamic and actually rendering the "wrapped" styles and content.
The custom element is not so great with dynamic content. There are edge cases where it might not work exactly as expected, but it has it's own advantages - it works with vanilla html, and it's more performant.
Gotchas and limitations of <FluidFlexbox>
So yeah that's the obvious thing .. the copies - you might be hesitant to triple the size of your sub tree... 😨 .
In reality browser can handle a lot of dom and a lot of components, in practice this should really not affect most use cases, but it's something to be aware of.
I would think twice before putting in near the root of a deep DOM tree. It will most likely work, but depending what's going on in this tree it's possible it will affect page performance.
Also if there is a lot of re-sizing going on where it's used, like animations for example, you might want to consider throttling or de-bouncing the monitoring (there are props for that).
The React version actually needs to re-render it's content 2 times, so if there are any heavy side effects running on those re-renders it might be noticeable (in general though you should structure your code to avoid side effects on render, but we all know it's sometime still happens in practice).
In this case the html component might be helpful if the React rendering is "heavy" - as it only clones the raw DOM and doesn't execute any rendering code.
Conclusion
The tools
React component and HTML custom element
utilize the technique described above to make building responsive, fluid layouts much easier.
They work intuitively as an extension of CSS Flexbox and add flex-wrap superpowers 🦸♂️ to enable complex, dynamic layout adaptations. There’s no need to write tricky custom code, and they can even be used without any JavaScript—just import the custom element and rely on CSS.
Of course, there are some drawbacks - it’s not native behavior - but the capabilities are there.
I’ve been using this technique in production for tasks like collapsing button labels into icons and implementing "greedy navigation" (moving items into a dropdown menu when they overflow), and it’s worked very well for me so far.
I believe this could be incredibly useful to many developers and significantly improve how we execute responsive design. Who knows —maybe in the future, we’ll get native ::collapsed
pseudo-class support in CSS 🙏.
Top comments (0)