Why Your Three Flex Columns Aren't Equal
You’ve built the hero section. Now comes that ubiquitous three-column block. You reach for display: flex, expecting three neat, equal columns. Instead, flexbox delivers columns whose widths are dictated by their content, not by any sense of visual balance.
This isn’t a bug. It’s the core behavior of flexbox, and it stems from how the layout engine calculates an item’s initial size. Most tutorials show simple flex items shrinking to fit their content, which gives the impression that flexbox wants things to be as small as possible. In reality, flexbox starts from a much larger assumption: it wants items to be as wide as their intrinsic maximum content size.

The Intrinsic Base Size
Before flexbox even considers fitting items into a container, it assigns each one a base size. That base size is the element’s max-content width—the width the content demands if it never wraps. This means a short paragraph produces a narrow item, while a long, unbroken string of text produces a much wider one. In many demo cases, this looks like a simple shrink-to-fit effect. But it’s really the browser measuring content as if it had infinite space.
This is where a second default property comes into play: flex-shrink. Its value defaults to 1, and its job starts when the combined max-content widths of all items overflow the container. In that scenario, items are allowed to shrink to prevent overflow. If no overflow occurs, flex-shrink does nothing at all.
So, an un-styled three-column layout results in columns whose widths simply reflect their own content, because the flexible container’s job is to place those content-sized items side by side, only applying flex-shrink when necessary to fit the parent.
How Equal Shrinking Isn’t Equal Sizes
To make this visible, it helps to remove content from the equation entirely. Imagine a 600px flex container holding three items: two with a width: 300px and one with a width: 600px, with no gaps or padding. The total base size is 1200px—double the container’s width.
Because the default flex-shrink is 1 on all items, they all shrink at the same rate. Crucially, rate here doesn’t mean amount. Each item’s size is divided by 2, so the two 300px items become 150px, and the 600px item becomes 300px. The math works perfectly because the total base size is twice the available space.


300px become 150px.Flexbox isn’t aiming for equal final widths; it’s ensuring proportional fitting. This is the system’s biggest challenge when trying to build a symmetric, content-driven column layout.
Common Fixes That Fall Short
The flex: 1 Myth
A widely used shortcut is declaring flex: 1 on each item, with the expectation that they will all grow equally to fill available space. This shorthand sets flex-grow: 1, but developers often miss its subtle side effect on the other two shorthand values. When you use flex: 1, you’re not just turning on growth; you’re also resetting flex-basis to 0%. If you omit values in the flex shorthand, they won’t stay at their defaults. This rewrites each item’s base size to 0, meaning flex-shrink becomes irrelevant—there’s no overflow to control—and the items grow from zero to fill the space equally.
.selector {
flex: 1;
/* flex-grow: 1; */
/* flex-shrink: 1; */
/* flex-basis: 0%; Wait what? */
}
In theory, this results in equal column widths. In practice, however, it fails when items carry padding or border.
There’s a strange quirk and a good reason behind it: when flexbox distributes space, it calculates based strictly on each item’s content-box, not its border-box. Even with a global reset like * { box-sizing: border-box; }, flexbox’s internal division of space ignores padding and border. If it deferred to the box model, it couldn’t fit items without awkwardly shrinking their padding or forcing overflow—both undesirable outcomes. So it only deals in content-box widths.
Add padding: 1rem to a flex: 1 item, and the math starts to skew. If the container is 600px, the free space isn’t 600px; it’s 600px minus the 32px of padding (roughly). The available 568px gets divided equally among items, but the element with padding (whose content box is its starting width plus its padding split across all items) ends up with an inconsistent final size. The result is a noticeable misalignment that defeats the purpose of the equal-column pattern.
.flex-parent { display: flex; }
.flex-parent > * { flex: 1; }
The flex-basis: 100% Illusion
A more refined approach seems to be setting flex-basis: 100% on each item. Here, each item’s ideal width is the full container: 600px. With three items, that’s 1800px of demand against 600px of space, so flex-shrink kicks in heavily. But the same content-box quirk applies. An item with padding: 1rem has a flex-basis of 568px, since the 100% of 600px must occupy the border-box. Flex’s shrink logic divides that differently. The difference grows imperceptibly small in large containers, but math still separates the columns’ widths.
.flex-parent {
display: flex;
}
.flex-parent > * {
flex-basis: 100%;
}
Grid’s Cleaner Solution
These pitfalls raise a natural question: why keep boxing with flexbox when CSS Grid was designed for equal tracks?
Setting display: grid alongside grid-auto-flow: column gives a same-row layout that looks similar to flexbox. But grid’s core difference is that track sizing is decided solely by the parent, ignoring the content that lives within each track. With just one more rule, you can enforce exact equality:
.grid-container {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
}
Adding grid-auto-columns: 1fr tells the grid to allocate each auto-created column an equal share of free space. These fr units are literally called “flex units” in the specification. Yet unlike flexbox’s calculated shrink or grow factors, they don’t look at an item’s padding or border. One unit simply equals the other units. The math is inherent, transparent, and consistent.
.grid-container {
display: grid;
grid-auto-flow: column;
}
Grid also benefits from its auto-placement algorithm. All rules reside on the parent, which means content on individual items can’t secretly undermine your layout. Wrapping grids down to small screens is also routine; switching column heights is a natural part of defining fixed track lists and does not require reversing any mental model.
By using a system that hands supreme control of column widths to the container, padding no longer becomes an obstacle for perfect, equal columns.
Flexbox’s Real Value
None of this is an argument for abandoning flexbox. Its strength—precisely the behavior that trips up the equal-column pattern—is its reliance on intrinsic size. Flex works brilliantly when you want items to hug their content naturally, like navigation links, button groups, or any row of mixed-width actions. The auto-adjusting spaces around uneven items are exactly what you want.
The requirement here is not blending. If you need equal-width columns that ignore your content’s inherent sizes, grid is more than up to the task—and it’s remarkably intuitive once you separate grid’s layout mechanism from the rigid, full-page grids familiar from early CSS Grid.



