Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. Flexbox vs Grid: how to choose in ten seconds
Web Development

Flexbox vs Grid: how to choose in ten seconds

Flexbox vs grid: flex sizes items to their content, grid sizes the tracks. Measured, that gave 32px and 229px against three equal 193px columns.

By Max Arthur
Co-Founder & Content Marketer·September 4, 2026·11 Min read
Three flex children sized unevenly by their content beside three grid children sized equally by their tracks

The usual answer is "flexbox is one-dimensional, grid is two-dimensional". True, and not much help when you are staring at a row of three boxes.

Here is a more useful version, and it is measurable. Flex sizes items to their content. Grid sizes the tracks, and the content fits into them.

Three children with different amounts of text, in a 600px container:

.f { display: flex; width: 600px; gap: 10px; }
.g { display: grid; grid-template-columns: repeat(3, 1fr); width: 600px; gap: 10px; }
Layout Child widths
Flex 32, 229.2, 24.9
Grid (1fr each) 193.3, 193.3, 193.3

Identical markup. Flex gave each child the width its content needed. Grid gave each child a third of the space regardless of what was in it.

That is the ten-second question: do you want the content to decide the sizes, or do you want to decide them?

Content-driven or layout-driven

Flexbox starts from the items. Each one is sized by its content, then leftover space is distributed according to flex-grow, or removed according to flex-shrink. The container reacts to what is inside it.

Grid starts from the container. You declare the tracks, and items are placed into them. The content reacts to the structure.

Both are correct in different situations, and the framing that actually decides it is:

  • Do the items need to line up with items in other rows? Grid. That alignment is what a grid is.
  • Do you just need these things in a row or a column, spaced sensibly? Flex.

A navigation bar is flex: the links are whatever width their text needs, and nothing below them has to align. A card grid is grid: every card in column two should share an edge.

The wrapping difference

Both can wrap, and they wrap differently. Three items in a 300px container:

.fwrap { display: flex; flex-wrap: wrap; width: 300px; gap: 10px; }
.fwrap > div { flex: 1 1 120px; }

.gauto { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
         width: 300px; gap: 10px; }

Measured positions and widths:

Row 1 Row 2
Flex 145, 145 300
Grid 145, 145 145

The third flex item stretched to the full 300px on its own row. The third grid item stayed at 145px, keeping the column it belongs to.

This is the single most visible difference in everyday work. A wrapped flex row leaves a widow that fills the line, because flex-grow distributes the leftover space among whatever is on that line. A grid keeps its columns, because the columns exist independently of how many items there are.

Neither is wrong. A wrapped tag list often looks better with the last item stretched; a product grid almost never does. If a lone item on the last row stretching bothers you, you wanted grid.

Grid can overlap, flex cannot

Two grid items placed in the same cell:

.overlap { display: grid; }
.overlap > div { grid-area: 1 / 1; }

Measured, both rendered at x: 0, y: 146, width: 200: the same box, stacked. Flex has no equivalent; items are laid along an axis and cannot occupy the same position.

This is the tidy modern way to layer a caption over an image, or put a loading state on top of content, without absolute positioning and its anchoring requirements. Both items stay in the flow, so the container still sizes itself to the larger of them.

What each one is actually good at

Reach for flex when:

  • Laying out a row of buttons or nav links
  • Vertically centring one thing inside another
  • A toolbar where one item should push the rest to the right (margin-left: auto)
  • Anything where the number of items varies and they should just fit
  • Sizing by content is the point

Reach for grid when:

  • A page layout with a header, sidebar, content and footer
  • A card grid where columns must line up
  • Any two-dimensional arrangement
  • You want to declare the structure once and place things into it
  • Overlapping elements without positioning

How each one sizes things

The mechanisms behind the measurements above, because knowing them is what makes the choice automatic.

Flexbox sizes from the item. Each item starts at its flex-basis, which defaults to auto, meaning its content width. The container then adds leftover space according to flex-grow, or removes overflow according to flex-shrink.

flex: 1;            /* grow, shrink, basis 0: equal shares regardless of content */
flex: 1 1 auto;     /* grow and shrink, but start from content width */
flex: 0 0 200px;    /* fixed, never grows or shrinks */

flex: 1 and flex: 1 1 auto are often assumed to be the same and are not. flex: 1 sets the basis to 0, so every item starts from nothing and shares the space equally. flex: 1 1 auto starts from content width and shares only the surplus, so items with more text stay wider.

Grid sizes from the track. The container declares the columns and items are placed into them:

grid-template-columns: 200px 1fr 200px;         /* fixed, flexible, fixed */
grid-template-columns: repeat(3, 1fr);          /* three equal */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));   /* as many as fit */

1fr means one share of the leftover space, which is why the three grid children measured identically regardless of their content.

There is a trap in 1fr worth knowing. It means minmax(auto, 1fr), and that auto minimum stops a track shrinking below its content. A long unbroken string therefore blows out a 1fr column. minmax(0, 1fr) removes the floor and is what you usually want when the content might be wide, which is the same problem as min-width: 0 on a flex item.

Alignment: the same words, two contexts

Both use the same property names, and grid adds a second level.

justify-content;  /* the whole group, along the inline axis */
align-content;    /* the whole group, along the block axis */
align-items;      /* each item within its line or track */
justify-items;    /* grid only: each item within its column */
align-self;       /* one item, overriding align-items */
justify-self;     /* grid only: one item within its column */

In flexbox, justify-content distributes the items along the main axis, so it is what produces space-between navigation bars. In grid it distributes the tracks inside the container, which only does anything when the tracks are narrower than the container.

The practical difference: grid can place a single item precisely within its own cell with justify-self, and flexbox has no equivalent, because a flex item has no cell to sit in. When you find yourself wanting to align one item independently on both axes, that is grid's job.

place-items: center is the shorthand for both axes and is the shortest way to centre something in either layout mode.

Subgrid, and the problem it fixes

The one thing grid could not do until recently: align a child's contents with the parent grid.

Three cards in a row, each containing a title, body and button. Without subgrid, each card is its own layout, so a two-line title in one card pushes its button lower than the others.

.card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
}

The card now uses the parent's row tracks rather than defining its own, so every title, body and button lines up across all three cards. Before subgrid the workarounds were fixed heights or JavaScript measurement, both of which broke when the content changed.

This is worth knowing as the answer to "my card buttons do not line up", which is one of the most common layout complaints and had no clean fix for years.

They are not competitors

The most useful thing to know is that almost every real page uses both. Grid for the page skeleton, flex inside the components:

.page {
  display: grid;
  grid-template-columns: 250px 1fr;
  gap: 2rem;
}

.card-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
}

A grid item can itself be a flex container, and a flex item can be a grid. Nesting them is normal rather than a compromise.

The properties that work in both

Worth knowing, because it reduces how much you have to remember:

gap: 1rem;              /* spacing between items, both */
justify-content: ...;   /* distribute along the main/inline axis, both */
align-items: ...;       /* align on the cross/block axis, both */
place-items: center;    /* shorthand for both axes, both */

gap in particular was grid-only for years and now works in flexbox everywhere. Any tutorial telling you to use margins on flex children and strip the last one is describing a workaround that is no longer needed.

The justify and align families mean the same thing in both, with one difference: in flexbox they apply to the whole line, while in grid they apply to the tracks, and grid additionally gives you justify-items and justify-self for placing an item within its own cell.

Choosing quickly

Situation Use
Nav bar, button row, toolbar Flex
Centring one element in another Flex, or place-items: center on a grid
Page skeleton Grid
Card grid with aligned columns Grid
Tag list that should wrap and fill Flex with flex-wrap
Responsive columns with no media queries Grid with auto-fit and minmax
Layering two elements Grid, same grid-area
Sidebar that shrinks but never below 200px Grid with minmax(200px, 250px)

That last pair is where grid quietly wins. repeat(auto-fit, minmax(200px, 1fr)) builds a responsive grid that adds and removes columns as space allows, with no media queries at all. Doing the same in flexbox needs flex-basis guesses and still leaves the stretched-widow behaviour.

Converting a flex layout to grid

The most common reason to switch is the wrapped-widow behaviour measured above. The conversion is usually shorter than the original.

/* flex version */
.list { display: flex; flex-wrap: wrap; gap: 1rem; }
.list > * { flex: 1 1 250px; }

/* grid version */
.list { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; }

Two declarations instead of three, no rule on the children at all, and the last row keeps its column widths.

auto-fit and auto-fill differ in one way worth knowing. auto-fit collapses empty tracks, so three items in a container wide enough for five stretch to fill it. auto-fill keeps the empty tracks, so those three items stay at their minimum and leave a gap on the right. Pick auto-fit for a grid that should always look full, and auto-fill when column positions should stay put regardless of how many items there are.

What each one still cannot do

Flexbox cannot align across lines. Items on the second row of a wrapped flex container know nothing about the items above them, which is the whole reason the widow stretches. Grid tracks exist independently of the items, so alignment is automatic.

Grid cannot size a track to one specific item's content. Tracks are sized from all the items in them, so "make this column exactly as wide as the widest button" needs max-content, which sizes to the widest content in the whole track rather than one chosen item.

Neither reflows content between columns. For a block of text flowing from one column into the next, the tool is CSS multi-column layout (column-count), which is a third layout mode that neither replaces.

Knowing the boundaries matters because a lot of time gets lost trying to make one of them do the other's job, and the answer is usually to nest them rather than to fight.

Three common mistakes

Using flex for a card grid. It works until the last row has fewer items than the others, at which point they stretch and the alignment you wanted is gone. Grid keeps the columns.

Using grid for a nav bar. You end up declaring track sizes for content whose width you do not know and do not care about. Flex sizes it for you.

Believing you must pick one. They solve different problems and compose well. Grid outside, flex inside is the default arrangement for most pages.

Quick reference

/* one axis, content-driven */
display: flex;
gap: 1rem;
justify-content: space-between;
align-items: center;

/* two axes, track-driven */
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;

/* responsive with no media queries */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

/* layer two items */
.overlap > * { grid-area: 1 / 1; }

Want to build both and watch them behave differently on the same markup? Start with the CSS track, or read the complete flexbox guide for flexbox on its own.

Keep reading

More from the blog

Two competing CSS rules with their specificity scored as three numbers, showing which one wins and why
August 18, 2026·6 min readWeb Development

CSS specificity: why your style is not applying

Specificity is three numbers compared left to right, not one score. Here are the contests that decide it, measured in a browser.

Read more
A single-colon selector targeting an existing element in a state beside a double-colon selector generating a new box
August 17, 2026·10 min readWeb Development

Pseudo-classes vs pseudo-elements: :hover and ::before

CSS pseudo-classes select an element in a state. Pseudo-elements create something never in your HTML, and on an image they silently do nothing.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs MimoDevpuff vs Sololearn
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs freeCodeCamp
Read All Comparisons