Building Website Templates with CSS Grid and Flexbox
CSS Grid handles the page skeleton in two dimensions, and flexbox lines up the pieces inside it. Together they replace the floats, clearfixes and width arithmetic that older templates depended on.
Why floats had to go
For years, side-by-side columns were built with floats. A designer gave each column a percentage width, floated it left or right and added a clearfix so the parent would not collapse to zero height. It worked, mostly, but it was never what floats were for. They were meant to let text wrap around an image.
The cracks showed quickly. Percentages that added up to slightly more than 100 once borders and padding were counted pushed the sidebar below the content. Equal-height columns needed tricks. Vertical centering needed more tricks.
Flexbox and Grid solve these problems directly. Flexbox arranges items along one axis, a row or a column. Grid arranges them in rows and columns at once. Neither needs a clearfix, and both size their children from the space available.
Flexbox for headers and menus
A site header with a logo on one side and a menu on the other is a one-dimensional problem, which makes it a natural fit for flexbox.
<header class="site-header">
<a href="/" class="logo">Site name</a>
<nav class="main-nav" aria-label="Main">
<a href="/">Home</a>
<a href="/articles/">Articles</a>
<a href="/contact/">Contact</a>
</nav>
</header>
.site-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 1rem clamp(1rem, 4vw, 2rem);
}
.main-nav {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
justify-content: space-between pushes the logo and the menu to opposite ends, and align-items: center centers them vertically, whatever their heights. The flex-wrap lines let the menu drop below the logo on narrow screens instead of overflowing. The clamp() value scales the side padding with the window but keeps it between 1rem and 2rem.
Add or remove a menu item and the rest adjust on their own. No widths to recalculate.
Grid for the page skeleton
A main column with a sidebar is two-dimensional once you count the header and footer, and Grid describes it in a few lines.
<div class="page">
<main class="content">
<h1>Article title</h1>
<p>Main content.</p>
</main>
<aside class="sidebar">
<h2>Related</h2>
<p>Supporting links.</p>
</aside>
</div>
.page {
display: grid;
gap: 2rem;
max-width: 75rem;
margin-inline: auto;
padding: 2rem 1rem;
}
@media (min-width: 48rem) {
.page {
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}
}
On small screens the grid has one column and the sidebar sits below the content. From 48rem up, the fr units share the width: two parts for the content, one for the sidebar. Wrapping them in minmax(0, ...) lets a column shrink below the width of its longest unbreakable content, such as a long URL or a wide code block, which otherwise forces the column wider than its share.
Grid can also place items in a different visual order from the source. Use that carefully. Keyboard focus and screen readers follow the HTML order, so a layout that looks reordered but tabs in the original sequence is confusing. Keep the source order logical and move things visually only when the reading order still makes sense.
Responsive card grids without media queries
Card layouts used to need a media query for each column count. Grid can work out the number of columns by itself:
<section class="cards">
<article class="card">First card</article>
<article class="card">Second card</article>
<article class="card">Third card</article>
<article class="card">Fourth card</article>
</section>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
gap: 1.5rem;
}
.card {
padding: 1.5rem;
border: 1px solid #d4d4d4;
border-radius: 0.5rem;
}
repeat(auto-fit, ...) creates as many columns as fit in the container. Each one is at least 15rem wide, and 1fr lets them share any leftover space. The min(15rem, 100%) part handles very small screens: if the container is narrower than 15rem, a column takes the full width instead of overflowing. On a wide screen you get four cards in a row, on a tablet two or three, and on a phone one, all from one rule.
Named areas for readable layouts
For a page with several regions, line numbers get hard to follow. Grid lets you name areas and draw the layout as text instead:
.layout {
display: grid;
grid-template-areas:
"header"
"main"
"aside"
"footer";
}
@media (min-width: 60rem) {
.layout {
grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
grid-template-areas:
"header header"
"main aside"
"footer footer";
}
}
Each child then takes its place with a single declaration, such as grid-area: main on the main element. The advantage shows when the design changes. To move the sidebar to the left on wide screens, you swap two words in the template rather than recalculating column numbers. The narrow version stacks the regions in the same order as the HTML, which keeps the reading order and the visual order in step. Anyone opening the stylesheet later can read the intended layout at a glance, which is worth a lot in a template that other people will customize.
Spacing with gap
Spacing between items used to mean margins on every child and then a selector to remove the margin from the last one. The gap property sets space only between items, never on the outer edges, and it works in both grid and flex containers.
.stack {
display: flex;
flex-direction: column;
gap: 1rem;
}
Every child in this stack is separated by exactly 1rem, with nothing extra at the top or bottom. That is easier to reason about than collapsing margins and needs no special cases.
Putting it together
A full page combines both tools. Grid sets up the overall frame, and flexbox handles the components inside it.
body {
margin: 0;
min-height: 100vh;
display: grid;
grid-template-rows: auto 1fr auto;
}
.site-header,
.site-footer {
padding: 1rem clamp(1rem, 4vw, 2rem);
}
The three rows are the header, the main area and the footer. Because the middle row is 1fr and the body is at least the height of the window, a short page still keeps its footer at the bottom of the screen. Inside the middle row, the .page grid from earlier positions content and sidebar, the .cards grid arranges any card lists, and flexbox lines up the header and menu.
The division of labor is easy to remember: Grid for the layout, flexbox for the pieces. Templates built this way have shorter stylesheets, fewer breakpoints and no clearfix hacks, and they hold together across the full range of screen sizes. Grid, flexbox, gap, minmax() and clamp() are all supported in current versions of the major browsers, so there is little reason to keep float layouts in a new template.
Frequently asked questions
Is CSS Grid better than flexbox?
They solve different problems. Grid is built for two-dimensional layouts such as the overall page or a card grid, while flexbox suits a single row or column, such as a menu or a toolbar. Most templates use both.
Why move away from floats for page layout?
Floats were designed to wrap text around images. Using them for columns requires clearfixes and careful width math, and small rounding differences can push a column onto the next line. Grid and flexbox handle alignment and spacing directly.
What is the difference between auto-fit and auto-fill?
Both create as many columns as fit. With auto-fit, empty tracks collapse and the existing items stretch to fill the row. With auto-fill, empty tracks keep their space, so a short row of items does not stretch.
Do grid and flexbox still need vendor prefixes?
Not for current browsers. Grid, flexbox and the gap property are supported unprefixed in all current major browsers.