On a client's custom theme I needed a two-column section: the left column was content-heavy (long paragraphs, an image), the right column was lighter (just a few small items). Simple in my head, annoying in CSS. Both columns had to be the same height, and the items in the lighter column had to spread out to fill that height, not stack at the top and leave a gap below.
My first attempt was naive. I gave the inner content of the light column max-height: 100%, hoping it would "inherit" the height of the heavy column:
.col-light {
max-height: 100%;
}
.col-light .inner {
max-height: 100%;
}The result: the content collapsed. The height never grew, the items still stacked at the top. No console error, just a layout that looked wrong and left me scratching my head.
Why this happens
max-height: 100% is a percentage relative to the parent's height. The trouble is that here the parent's height isn't set explicitly, it's defined by its own content. So you get a circular dependency: the child's height depends on the parent's height, while the parent's height depends on the child's height. CSS has no concrete number to resolve against, so the percentage is ignored and the element collapses to its natural height. That's why the value seemed to do nothing at all.
My second attempt was worse in principle: measure and clamp with JavaScript. Read the left column's offsetHeight, then set the right column to that number.
function syncHeights() {
const left = document.querySelector('.col-heavy');
const right = document.querySelector('.col-light');
right.style.height = left.offsetHeight + 'px';
}
window.addEventListener('resize', syncHeights);
syncHeights();Technically this "worked," but it was brittle. It lagged on resize, visibly chasing the layout a fraction of a second after the viewport changed. It also fought layout shifts: when a font finished loading or an image finally decoded, the height I'd hardcoded was suddenly wrong until the next resize event fired. Measuring heights in JavaScript for something that's genuinely a CSS job is almost always a sign I'm swimming against the current.
The fix
The key was to separate two problems I'd been mashing together. The first, "make both columns the same height," is a Grid job. The second, "distribute the children of the light side across that height," is a Flexbox job. Each has a built-in tool for exactly that, and neither needs a single line of JavaScript.
CSS Grid uses align-items: stretch by default. That means every grid item is automatically stretched to fill the full height of its row. When two columns sit in the same row, the row is as tall as the tallest item, and both items grow to that height. So equal height is free, just use Grid:
.row {
display: grid;
grid-template-columns: 1fr 1fr;
}Now the light column has a concrete, stretched height, the very thing max-height: 100% never had. All that's left is to spread its children. This is where Flexbox comes in: make the light column a vertical flex container, then use justify-content: space-between so its children fan out from top to bottom across the full height:
.col-light {
display: flex;
flex-direction: column;
justify-content: space-between;
}Done. The first item sticks to the top, the last item sticks to the bottom, and the rest are evenly spaced between. No measuring, no offsetHeight, no resize listener. And most importantly, it reflows correctly on resize, because everything is computed by the layout engine in the same frame instead of being chased by JavaScript one frame behind.
One small gotcha that trips people up: align-items: stretch only kicks in if the item itself doesn't have an explicit height. So don't put a height on the column. Let Grid decide the height, and let the inner flex divide it. If I want a minimum gap between items instead of pure space-between, a gap on the flex container still respects this distribution.
The final result in the browser was exactly what the client asked for: the right column, holding just three small items, now spread neatly down the full height of the content-heavy left column, and when the screen shrank everything re-laid-out on its own without a flicker.
The takeaway
When two columns must be the same height and one of them needs to fill that height, don't measure anything in JavaScript. Split the job: use Grid's built-in stretch to equalize the column heights, then use a flex column with justify-content: space-between on the lighter side to distribute its children across that now-stretched height. max-height: 100% fails because it needs a concrete parent height that never exists inside a circular dependency; Grid supplies that concrete height, and Flexbox makes use of it. Two layout tools each doing the one thing they were designed for will always beat one script trying to guess heights.
