How would you build a holy-grail page layout — header, footer, fixed-width left and right sidebars and a fluid centre column — with Flexbox, and when would you reach for CSS Grid instead?
answer
- two axes means two containers
- the wrapper div is the tell
- fixed basis, no grow, no shrink
- one dimension versus two
- sizing on the parent or on each child
basics
~20 sNest two flex containers: an outer column for header, body and footer with min-height: 100vh, and an inner row where each sidebar is flex: 0 0 240px and the centre is flex: 1. Grid is better once the layout is genuinely two-dimensional or must rearrange at breakpoints.
solid answer
~50 sWith flexbox it takes two nested containers. The outer one is `flex-direction: column` with `min-height: 100vh`, holding header, a body wrapper with `flex: 1`, and footer. The body wrapper is `flex-direction: row`, with each sidebar `flex: 0 0 240px` — no grow, no shrink, fixed basis — and the centre column `flex: 1` so it takes whatever is left. It works, and each container is genuinely one-dimensional, which is what flexbox is for. I reach for grid when the layout is two-dimensional: one container with `grid-template-columns: 240px 1fr 240px` and `grid-template-areas` expresses the whole page without the extra body wrapper, keeps the intent on the parent, and lets me re-map the same markup into a stacked mobile layout by rewriting the areas in a media query. Flexbox stays the better fit for genuinely linear strips like the header's own contents.
code
css · 19 lines.page {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.page__body {
flex: 1;
display: flex;
}
.page__nav,
.page__aside {
flex: 0 0 240px;
}
.page__main {
flex: 1;
}go deeper
Be able to sketch the nesting: an outer column container for header, body and footer, an inner row container for the three columns. Say that fixed sidebars use flex: 0 0 with a basis, and the centre uses flex: 1.
Explain what flex: 0 0 240px expands to and why width alone lets the sidebar shrink. Be ready to state that flexbox is one-dimensional, which is why the layout needs a second container.
Demonstrate the tool choice with a rule, not a preference: two constrained axes means grid, one line means flex. Show how each version responds to a mobile breakpoint and where the sizing information lives for the next engineer to read.
Own the shell decision for a codebase: one grid skeleton with named areas versus nested flex wrappers, how it survives RTL, theming and per-route variation, and the migration cost of changing it later. Be able to say where the flex-inside-grid boundary sits.
## The flexbox implementation "Holy grail" is the old name for a page with a header, a footer, two fixed-width sidebars and a fluid centre that fills the remaining height and width. The flexbox version is two nested containers, each handling one axis. ```css .page { min-height: 100vh; display: flex; flex-direction: column; } .page__body { flex: 1; display: flex; } .page__nav, .page__aside { flex: 0 0 240px; } .page__main { flex: 1; } ``` The outer column does the vertical work — `flex: 1` on the body wrapper is the same sticky-footer mechanic that keeps the footer at the viewport bottom on short pages. The inner row does the horizontal work. `flex: 0 0 240px` expands to `flex-grow: 0; flex-shrink: 0; flex-basis: 240px`: the sidebar neither claims free space nor gives any up, so it stays exactly 240px while the centre absorbs everything else. Writing `width: 240px` instead is the usual bug, because `flex-shrink` still defaults to 1 and the sidebar quietly narrows when the centre column's content is wide. Sidebars are equal height for free — the row container's default `align-items: stretch` makes each column as tall as the tallest, which is what "equal-height columns" meant back when this layout needed floats and negative margins to achieve at all. ## Why this needs a wrapper element The layout has two axes: a vertical stack and, inside one band of that stack, a horizontal split. Flexbox is one-dimensional by design — a container lays out along a single axis — so expressing two axes requires two containers, and therefore an element (`.page__body`) that exists only to be the second one. That extra div is not a crime, but it is the tell: whenever you find yourself adding wrappers purely so a second flex container has somewhere to live, the layout is two-dimensional and you are working against the model. ## The grid implementation ```css .page { min-height: 100vh; display: grid; grid-template-columns: 240px 1fr 240px; grid-template-rows: auto 1fr auto; grid-template-areas: "header header header" "nav main aside" "footer footer footer"; } .page__header { grid-area: header; } .page__nav { grid-area: nav; } .page__main { grid-area: main; } .page__aside { grid-area: aside; } .page__footer { grid-area: footer; } ``` One container, no wrapper, and the layout is legible as a picture in the source. The `1fr` column and `1fr` row take the leftover space exactly as `flex: 1` does in the flex version, so the footer still sits at the viewport bottom on short pages. ## The deciding question The honest rule is: **does the layout constrain both axes at once?** If the answer is yes — rows and columns that must line up with each other — grid is the tool. If a container arranges children along one line, however that line is styled, flexbox is the tool. A page shell almost always answers yes; a toolbar, a button group, a media object, a tag list or a card's internal stack almost always answers no. Two secondary factors usually settle borderline cases. **Responsive rearrangement.** Collapsing the holy grail to a single stacked column on mobile is one rewritten `grid-template-areas` block inside a media query, with the markup untouched. The flex version needs `flex-direction: column` on the body wrapper plus per-child order or size changes, and the resulting rules are spread across parent and children rather than living in one place. **Where sizing lives.** In grid, track sizes are declared on the parent, so you read the layout in one rule. In flex, each child declares its own share via `flex`, so understanding the layout means reading every child. For a page shell the grid version is easier to hand to another engineer; for a strip whose children genuinely have different sizing behaviour, per-child `flex` is the more natural expression. ## Where flexbox still wins in this page Grid for the shell does not mean grid everywhere. The header's own contents — logo, nav links, an account button pushed to the end with an auto margin — are a one-dimensional strip, and flexbox with `gap` is the right tool. Real applications use grid for the page skeleton and flex inside most of the boxes it creates, and being able to say that split cleanly is what the question is really probing. ## Legacy context The layout earned its dramatic name in the float era, when achieving it meant negative margins, a fixed-height footer, and source order that put the centre column first for SEO at the cost of comprehensibility. Flexbox reduced it to two nested containers; grid reduced it to one declaration block. Mentioning that history is fine as history — proposing floats as the technique today is not. ## A caveat on fixed sidebars `flex: 0 0 240px` (and a literal `240px` grid track) is rigid by design, which is correct for a real sidebar but becomes an overflow risk on narrow viewports. In grid the usual softening is `minmax()` on the track; in flex it is allowing shrink with an explicit `min-width` floor. Either way the responsible answer includes the breakpoint at which the sidebars stop being columns at all.
- Why flex: 0 0 240px on the sidebar rather than width: 240px?Because `flex-shrink` defaults to 1, so a sidebar sized only with `width` still shrinks below 240px once the centre column's content demands space — the classic "my sidebar keeps getting narrower" bug. The shorthand sets grow 0, shrink 0 and basis 240px explicitly, so the track is genuinely fixed until you decide otherwise at a breakpoint.
- How do you collapse this layout to a single column on small screens in each approach?With grid, rewrite `grid-template-areas` (and set `grid-template-columns: 1fr`) inside a media query — one rule, markup untouched, and you can reorder bands freely. With flex, switch the body wrapper to `flex-direction: column` and then revisit each child's `flex` value, so the change is spread across parent and children.
- Does choosing grid for the shell mean avoiding flexbox in the page?No — they compose. Grid handles the two-dimensional skeleton, and flexbox handles the one-dimensional strips inside it: the header's logo-and-links row, a card's vertical stack, a button group, a tag list. Reaching for flex inside grid cells is normal and is usually the clearer of the two for linear content.
saying these in an interview costs you the question
- Sizing sidebars with width alone and letting them shrink
- Proposing floats and negative margins as the current technique
- Adding wrapper divs indefinitely rather than switching to grid
- Claiming grid replaces flexbox everywhere
- Hard-coding sidebar widths with no narrow-viewport plan