CSS Layout (Flexbox & Grid)

Two modern CSS tools for arranging elements on a page without relying on old workarounds like floats.

What is it?

Arranging elements side by side, centering something both vertically and horizontally, or making a set of boxes evenly sized used to be genuinely difficult in CSS. Developers reached for tools that were never actually designed for layout — most famously the float property, originally meant for wrapping text around an image — and bent them into shapes they weren't built for, with lots of fragile side effects.

CSS eventually got two tools actually designed for arranging elements. Flexbox handles layout along a single direction — a row or a column — and is great for things like navigation bars, toolbars, or centering a handful of items. Grid handles two-dimensional layout — rows and columns at once — and is great for things like a page's overall layout, image galleries, or dashboards with defined regions.

Both work the same basic way: you turn on the layout mode on a container element, and its direct children automatically become items that get arranged according to rules you set — how they're spaced, aligned, sized, and ordered — instead of you calculating pixel positions by hand.

Explain like I'm 10

Flexbox is like arranging books along a single shelf — you can space them out or squeeze them together, but it's one line at a time. Grid is like arranging books in a bookcase with defined rows and columns, where you can also decide a single book spans multiple slots.

Examples

Flexbox: a simple row layout

.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

Turning on flex layout makes the toolbar's children line up in a row automatically. justify-content spaces them across the main axis, and align-items centers them on the cross axis, with no manual positioning needed.

Grid: a two-dimensional layout

.page {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
}

.sidebar { grid-column: 1; grid-row: 2; }
.content { grid-column: 2; grid-row: 2; }

This defines a page layout with a fixed-width sidebar column and a flexible content column, plus rows for a header, body, and footer, then places specific elements into specific cells of that grid.

How it works

When you set display: flex or display: grid on an element, the browser's layout engine switches from the normal block-by-block flow to one of these specialized layout algorithms for that element's children. Flexbox calculates how much space each item needs along a single axis and distributes any remaining or missing space according to your rules. Grid instead builds an explicit two-dimensional grid of rows and columns first, then places each child into a cell (or a span of cells) within it.

Why does it exist?

Flexbox and Grid exist because older layout tools were never designed for arranging general page content — they were repurposed for the job and required workarounds (clearing floats, fixed pixel widths, absolute positioning tricks) that broke easily as content changed. These two systems were purpose-built for layout, making common patterns like equal-height columns or centered content something you can express directly, instead of hacking around.

When to use it

Reach for Flexbox when arranging items in a single row or column — a nav bar, a button group, centering one thing inside another. Reach for Grid when you need to control both rows and columns at once — an overall page layout, a photo gallery, or any layout where content needs to align across both dimensions.

When not to use it

For simple in-line text flow — a paragraph, a sentence with a link in it — you don't need either; normal document flow already handles that. Overusing Grid for a layout that's really only ever one row or column also adds unnecessary complexity where Flexbox would be simpler.

Common mistakes

  • Reaching for float-based layout out of habit instead of Flexbox or Grid for new work.

  • Confusing Flexbox's single-axis model with Grid's two-axis model and trying to force complex two-dimensional layouts out of Flexbox alone.

  • Forgetting that flex/grid properties (like justify-content) go on the container, while sizing properties for individual items (like flex-grow) go on the children.

Practice exercises

  1. Easy:

    Use Flexbox to horizontally center three boxes inside a container, with even spacing between them.

  2. Medium:

    Build a page layout with a header, a fixed-width sidebar, and a flexible main content area using CSS Grid.

  3. Hard:

    Recreate a layout that was originally built with floats using Flexbox or Grid instead, and explain what workaround(s) you were able to remove.

Interview questions

What's the core difference between Flexbox and Grid?

Flexbox arranges items along a single axis (a row or a column). Grid arranges items across two axes (rows and columns) at once.

What CSS property turns an element into a flex or grid container?

display: flex for Flexbox, or display: grid for Grid, applied to the parent element whose children should be arranged.

Why were floats historically used for layout, and what was the problem?

Floats were originally designed for wrapping text around images, not for full-page layout, so using them for layout required extra workarounds like manually clearing floats to avoid broken, collapsing containers.

In Flexbox, what are the 'main axis' and 'cross axis', and how do justify-content and align-items relate to them?

The main axis runs in the direction set by flex-direction (row by default), and the cross axis runs perpendicular to it. justify-content aligns items along the main axis, while align-items aligns them along the cross axis.

If you switch `flex-direction` from `row` to `column`, what happens to the meaning of `justify-content` and `align-items`?

The main axis rotates to vertical, so justify-content now controls vertical spacing (top-to-bottom) and align-items now controls horizontal alignment — the two properties swap which physical direction they affect, because they're always defined relative to the main/cross axis, not to 'horizontal'/'vertical' directly.

What does the shorthand `flex: 1` expand to, and what does each part control?

It expands to flex-grow: 1; flex-shrink: 1; flex-basis: 0%. flex-grow lets the item grow to fill available space, flex-shrink lets it shrink if space is tight, and a flex-basis of 0% means its initial size before growing is treated as zero, so available space is distributed purely by the grow ratio.

What's the difference between `align-items` and `align-content` in Flexbox?

align-items aligns individual items along the cross axis within their line. align-content aligns entire lines of items along the cross axis when there's extra space and the container has multiple lines (via flex-wrap: wrap) — it has no visible effect on a single-line flex container.

Why might `justify-content: center` appear to do nothing on a flex container?

If the items have flex-grow set to fill all available space (e.g. flex: 1 on every item), there's no leftover space left on the main axis for justify-content to distribute, so it has nothing visible to do.

What's the difference between the `fr` unit in Grid and using percentages for column widths?

fr distributes the space that remains after fixed-size tracks (like px values or content-sized tracks) are subtracted, while percentages are always a fraction of the container's total size regardless of other tracks — which can cause overflow if fixed-size columns and percentages are combined and together exceed 100%.

Given `grid-template-columns: repeat(3, 1fr);` and one item with `grid-column: span 2`, what happens?

That item occupies two of the three equal-width columns in whatever row it's placed in, and the remaining items flow into the leftover single-column space, wrapping to new rows as needed via Grid's auto-placement.

What does `grid-template-areas` do, and why is it useful?

It lets you name regions of the grid as a visual ASCII-like map of strings, then place children into those named regions with grid-area, instead of tracking numeric row/column line numbers — making the overall layout structure much easier to read at a glance.

What's the difference between `justify-content` and `justify-items`/`justify-self` in Grid?

justify-content distributes the grid's tracks as a whole within the container (when tracks don't fill the container). justify-items/justify-self instead align an individual item within its own cell along the inline (row) axis, independent of how the tracks themselves are distributed.

How are absolutely positioned children treated inside a flex or grid container?

An absolutely positioned child is taken out of normal layout flow entirely — it doesn't participate in the flex or grid algorithm, doesn't get placed into a cell or take up main-axis space, and is instead positioned relative to its containing block as usual.

You set `align-items: center` on an outer flex container, but its content still isn't vertically centered — what's a likely cause?

align-items centers items within the height the container actually has. If the outer container's height is only as tall as its content (no explicit height, and it isn't itself being stretched by a parent), there's no extra vertical space to center within, so centering has no visible effect.

What is `flex-basis`, and how does it interact with `flex-grow` and `flex-shrink`?

flex-basis sets an item's initial main-axis size before any growing or shrinking is applied. flex-grow and flex-shrink then distribute any remaining or missing space (compared to that basis) among items according to their grow/shrink factors.

When would you use `flex-shrink: 0` on a flex item?

When you want that item to keep its natural size and never shrink even if the container runs out of space — for example, keeping an icon or a fixed-width sidebar from being squeezed as other flexible items compete for room.

Given `.item1 { flex: 1 }` and `.item2 { flex: 2 }` inside the same flex container, how is extra space divided between them?

The extra space is split proportionally to the grow factors — item2 receives twice as much of the leftover space as item1, so with equal starting size item2 ends up roughly twice as wide (or tall, in a column layout).

Why does adding `min-width: 0` (or `min-height: 0`) to a flex item sometimes fix an overflow or truncation bug?

Flex items default to min-width: auto, which prevents them from shrinking smaller than their content's natural (min-content) size — so long unbroken text or a wide child can force the item to overflow its container even with flex-shrink set. Explicitly setting min-width: 0 removes that floor, letting the item shrink further so text-overflow/ellipsis and wrapping can actually take effect.

What's the difference between the explicit and implicit grid in CSS Grid?

The explicit grid is made of the tracks you define with grid-template-columns/grid-template-rows. If items are placed outside that defined area (through auto-placement or explicit line numbers beyond it), the browser creates implicit tracks to hold them, sized by grid-auto-rows/grid-auto-columns (auto by default).

What does `grid-auto-flow: dense` change about item placement?

By default, Grid's auto-placement never backtracks — once it moves past a gap too small for an item, that gap stays empty. dense instead allows the algorithm to backfill earlier gaps with later items that fit, packing the grid more tightly at the cost of possibly placing items out of their source order visually.

When would you use `repeat(auto-fit, minmax(200px, 1fr))` instead of a fixed `repeat(4, 1fr)`?

auto-fit with minmax lets the number of columns respond to available space automatically — as many 200px-or-wider columns as fit are created and they stretch to fill any leftover space — giving a responsive grid without writing separate media queries for each screen width.

In a 900px-wide container with `grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));`, how many columns are created?

Four columns of 225px each. The browser fits as many 200px-minimum tracks as possible (four fit in 900px, since a fifth would need at least 1000px), then stretches them equally with the fr unit to fill the remaining space.

What does the `order` property do, and what's an important limitation of it?

It changes the visual order in which flex or grid items are painted, independent of their order in the HTML. It does not change the underlying DOM order, so keyboard tab order and screen reader reading order still follow the original markup — meaning order can visually reorder content in a way that no longer matches how it's read or tabbed through.

What's the difference between `gap` and using margins to space out flex or grid items?

gap applies space only between items, not around the outer edges of the container, and it doesn't require any special handling for the first/last item (unlike margins, which need something like :first-child/:last-child overrides to avoid extra space at the container's edges).

Why can a flex or grid item respond to `z-index` even without `position: relative` or `position: absolute` set on it?

Flex and grid items form a stacking context based on their order/z-index even while position: static, unlike ordinary block-flow elements, where z-index is ignored entirely unless the element has a position value other than static.

Why do flex items with different content heights end up the same height inside a row by default?

align-items defaults to stretch, which stretches every item to fill the container's cross-axis size unless the item has its own explicit height or align-self override — giving equal-height columns automatically, something float-based layouts couldn't do without extra hacks.

What happens to flex items when the container has `flex-wrap: nowrap` (the default) and they don't all fit on one line?

The items shrink (per their flex-shrink factor) to try to fit on the single line; if they can't shrink enough, they overflow the container rather than wrapping onto a new line, since wrapping is disabled.

What problem does CSS Grid's `subgrid` value solve?

Without it, a nested grid defines its own independent tracks, so its rows/columns can't align with the parent grid's tracks. subgrid lets a nested grid item adopt its parent's track sizing directly, so content inside it lines up with the outer grid instead of needing manually matched sizes.

Why can forcing a two-dimensional layout out of Flexbox alone become awkward?

Flexbox only manages one axis at a time — wrapped items form new lines, but items in different lines don't align with each other across that second axis the way Grid's explicit rows and columns do, so replicating true row/column alignment (like a calendar or dashboard grid) requires extra wrapper elements or fixed widths that Grid handles natively.

In Grid, what determines which cell an item lands in if you don't set `grid-column`/`grid-row` on it?

Grid's auto-placement algorithm places it into the next available cell, moving through the grid according to grid-auto-flow (row-by-row by default, or column-by-column with grid-auto-flow: column), creating implicit tracks if it runs out of explicitly defined ones.