Skip to content

Complex conditional width using flex-basis with clamp

Publish date: Author: Heydon

The primitive Sidebar layout is a kind of quantum layout whereby the sidebar only exists as a sidebar where there is available space. As the Sidebar document details, wrapping from a two-column to a one-column layout depends on the relationship between two things:

  1. A minimum percentage width for the non-sidebar element
  2. A preferred width for the sidebar element

The left configuration is in a wide context and the elements are next to each other. The right configuration is in a narrow context and the elements are above and below each other.

The breakpoint—if you want to call it that—is where (1) equals (2). When (1) becomes less than (2), the sidebar and non-sidebar stack vertically. The sidebar is no longer a sidebar.

(2) is either a set flex-basis value or defaults to the (maximum) width of the sidebar’s content (implicitly auto). Importantly, if the set value was a percentage, like (1), the breakpoint would never be breached and wrapping would not occur. Why? Because the sidebar would maintain its %-based share of the component in spaces of any size.

On the left, the layout is wide and the sidebar takes up 33%. On the right, the layout is narrow and the sidebar still takes up 33%

A flex-basis value of, say, 20ch differs because it’s a kind of quasi-fixed value. It will be exactly 20ch unless 20ch is either too small or too large.

.sidebar {
flex-basis: 20ch;
/* ↓ when not a sidebar, let it use up all the space */
flex-grow: 1;
}

On the left, in a wide layout the sidebar is 20ch. On the right, in a narrow viewport, 20ch is a smaller proportion of the layout width

But what if you wanted the sidebar to be 33.333% wide where possible? You can make flex-basis 33.333% then add a min-inline-size:

.sidebar {
flex-basis: 33.333%;
min-inline-size: min(20ch, 100%);
/* ↓ when not a sidebar, let it use up all the space */
flex-grow: 1;
}

The important part is using the target min-inline-size inside the min() function with 100%—a technique borrowed from the Grid layout. Since 100% is really the maximum value for the context, we are effectively toggling min-inline-size on and off, conditionally. Which is the kind of thing people don’t think CSS can do.

The wide layout has the sidebar at 33.333%, the medium layout has the sidebar at 20ch and the small layout has the sidebar at 100%

The slightly mind bending thing here is that the “minimum” value of `20ch` once hit immediately becomes greater than the larger value of `33.333%` that it replaces

This toggle ensures 20ch is only applied where that value is less than 100%. In other words, it stops stuff overflowing the viewport on small screens. Why don’t we use a max-inline-size declaration of 100% instead? Because, in the CSS standard, min-inline-size is designed to win out over max-inline-size, in all cases. Fortunately, the min() function is now well supported.

Another approach is to remove the min-inline-size declaration altogether and use clamp() for flex-basis:

.sidebar {
flex-basis: clamp(20ch, 33.333%, 100%);
}

The clamp() function clamps a value between upper and lower bounds. In this case, we prefer 33.333% but the width the 33.333% proportion represents should never be allowed to get smaller than 20ch or larger than 100%. Here’s the complete code for this specific take on the Sidebar component:

.with-sidebar {
display: flex;
flex-wrap: wrap;
gap: 1em;
}

.sidebar {
flex-basis: clamp(20ch, 33.333%, 100%);
flex-grow: 1;
}

.with-sidebar > :last-child {
flex-basis: 0;
flex-grow: 999;
min-inline-size: 50%;
}

The component’s algorithm is now more complex and nuanced than set out in the Sidebar layout. It can be expressed something like this: “Where the non-sidebar element is wider than the sidebar element (more than 50% of the whole component’s width), display the two elements side-by-side, wherein the width of the sidebar element is the smaller of 20ch and 33.333%. Where the non-sidebar element is narrower than the sidebar element (less than 50% of the whole component’s width), stack the two elements vertically, wherein the sidebar’s width will now be 100% whether 100% is wider or narrower than 20ch.”

That’s perhaps an unexpected amount of logic for a “declarative” language like CSS.


If you find yourself wrestling with CSS layout, it’s likely you’re making decisions for browsers they should be making themselves. Through a series of simple, composable layouts, Every Layout will teach you how to better harness the built-in algorithms that power browsers and CSS.

Buy Every Layout