In CSS, how do you build a sticky footer with Flexbox — one that sits at the bottom of the viewport when the page is short, but is pushed below the content when the page is long?
answer
- column container, not a fixed footer
- the wrapper supplies the free space
- min-height, never height
- one child grows, the rest don't
- auto margin on the footer also works
basics
~20 sMake the page wrapper a column flex container with min-height: 100vh, then give the main content flex: 1 so it absorbs the leftover vertical space. The footer then rests at the viewport bottom on short pages and flows below tall content.
solid answer
~40 sI make the outermost wrapper — often `body` itself — a column flex container with `min-height: 100vh`, then give the main region `flex: 1`. Header and footer keep their natural heights, and `main` grows to eat whatever vertical space is left, so on a short page the footer lands exactly at the bottom of the viewport. Because the wrapper uses `min-height` rather than `height`, a long page simply grows past the viewport and the footer flows below the content instead of overlapping it. An equivalent variant is `margin-top: auto` on the footer, which absorbs the free space from the other side. Note this is *not* `position: sticky` or `fixed` — the footer never leaves normal flow, so it doesn't cover content. On mobile, `100dvh` tracks collapsing browser UI better than `100vh`.
code
css · 11 linesbody {
margin: 0;
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}go deeper
Be able to write the recipe: a column flex wrapper with min-height: 100vh and flex: 1 on the main region. Say clearly that this is not position: fixed, because a fixed footer would cover content.
Explain where the free space comes from and why min-height beats height. Be ready to show the margin-top: auto variant and to state what flex: 1 expands to.
Show you can debug it in a real app: an unbroken height chain from the root element down, the dvh nuance on mobile, and whether the main region's background should fill the empty space. Interviewers look for the diagnosis, not the snippet.
Own where this lives: a single app-shell layout primitive versus per-page rules, and how it interacts with scroll containers, in-page anchors, and any framework root element you do not control. Be able to justify grid rows over flex for the shell.
## The problem being solved "Sticky footer" is an unfortunate name: it has nothing to do with `position: sticky`. The requirement is that a footer should never float in the middle of a mostly-empty screen — on a page with little content it should sit at the bottom edge of the viewport — while on a content-heavy page it must simply follow the content and be reachable by scrolling. A fixed footer fails the second half, because it permanently covers part of the page. ## The flex recipe ```css body { margin: 0; min-height: 100vh; display: flex; flex-direction: column; } main { flex: 1; } ``` Three ideas are doing the work. **The column direction.** With `flex-direction: column` the main axis runs vertically, so `flex-grow` distributes *vertical* free space. Header, main and footer become flex items stacked top to bottom. **`min-height: 100vh`.** This is the source of the free space. It says the wrapper is at least as tall as the viewport, but may be taller. Using `height: 100vh` instead is the classic bug: it caps the box at exactly one viewport, so long content overflows the wrapper and the footer ends up painted over the middle of the page. **`flex: 1` on main.** The shorthand expands to `flex-grow: 1; flex-shrink: 1; flex-basis: 0%`. Since `main` is the only item with a non-zero `flex-grow`, it receives all the leftover space, pushing the footer to the bottom. On a long page there is no leftover space, growth contributes nothing, and everything falls back to natural flow. A reasonable variant is `flex: 1 0 auto` on `main`, which starts sizing from the content instead of from zero. In practice both work here, because a flex item's automatic minimum size prevents `flex-basis: 0` from squashing tall content. ## The auto-margin variant ```css footer { margin-top: auto; } ``` Instead of asking `main` to grow, you ask the footer's top margin to absorb the free space. Auto margins in flex soak up whatever main-axis space is available, so the footer is shoved to the far end. This is handy when several children could plausibly grow, or when you don't want `main` to stretch its own background across the empty space — a real visual difference: with `flex: 1` the main region's background paints across the empty area, with `margin-top: auto` it does not. ## Where to put the wrapper Applying the flex container to `body` works and avoids an extra element, but only if nothing resets `body`'s display and if you remember `margin: 0`. In an app that mounts into a root node (a `#root` or `#__next` style container), the height chain must be unbroken: an intermediate `div` between `body` and your layout that has neither the flex container nor a height will collapse it. The pragmatic fix is to put `min-height: 100vh` and the column flex on the outermost element your framework actually renders, rather than on `body`. ## Viewport-unit nuance On mobile browsers `100vh` resolves against the *large* viewport — the height with browser UI collapsed — so a footer positioned with it can sit below the visible area until the user scrolls. `100dvh` (dynamic viewport height) tracks the currently visible height as the URL bar shows and hides; it has been supported across the major browsers since 2022. A common approach is `min-height: 100vh` followed by `min-height: 100dvh`, so browsers that understand the newer unit use it. ## Alternatives worth naming CSS Grid expresses the same layout declaratively with `grid-template-rows: auto 1fr auto` on a wrapper of the same `min-height: 100vh` — the `1fr` row takes the leftover space exactly as `flex: 1` does. Which you pick is mostly taste for a three-row stack; grid reads slightly better because the intent lives in one declaration on the parent instead of being split between parent and child. The pre-flex techniques — negative margins on a footer of known height, or the `display: table` sandwich — required you to hard-code the footer height, and they break the moment the footer wraps to two lines. Mentioning why they were fragile is a good way to show you understand what flex actually bought here. ## What to check when it misbehaves If the footer floats mid-page, the wrapper has no height. If the footer overlaps content on long pages, someone wrote `height` where `min-height` belongs. If nothing moves at all, the growing element is probably not a direct child of the flex container — `flex-grow` only applies to direct children, so a wrapper `div` between the container and `main` swallows the effect.
- Why min-height: 100vh rather than height: 100vh?`height: 100vh` fixes the wrapper at exactly one viewport, so content taller than the screen overflows the box and the footer is painted over the middle of the page. `min-height` sets a floor instead of a cap: the wrapper is at least a viewport tall, and grows freely with the content, which is precisely the "short page vs long page" requirement.
- What is the practical difference between flex: 1 on main and margin-top: auto on the footer?Both push the footer down. With `flex: 1` the main element genuinely becomes taller, so its background, borders and any internal centering span the empty space. With `margin-top: auto` main keeps its content height and the gap is empty margin. Pick based on whether you want main's background to fill the screen.
- How would you express the same layout in CSS Grid?Give the wrapper `min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto`. The `1fr` middle row consumes the leftover space just as `flex: 1` does, and the whole intent sits in one declaration on the parent rather than split across parent and child.
saying these in an interview costs you the question
- Reaching for position: fixed or position: sticky on the footer
- Using height: 100vh so long pages overflow the wrapper
- Putting flex: 1 on an element that isn't a direct flex child
- Hard-coding the footer height and offsetting with negative margins