CSS Website Layouts: Common Patterns and How to Build Them
Build responsive page shells, navigation, and card grids with CSS Grid and Flexbox. Learn when normal flow, positioning, and floats fit.
Use CSS Grid when page regions need to relate across both rows and columns, and use Flexbox when items need to line up or distribute space along one axis. Start with semantic HTML in normal flow, then add layout rules where the content needs them. A practical page often combines all three: Grid for the page shell and card collection, Flexbox for navigation and the contents of each card.
This guide builds a responsive page shell and card listing, explains how to choose breakpoints, and shows where positioning and floats belong. The examples are original and follow the layout concepts in MDN’s CSS layout introduction, its common grid layouts guide, and documentation for Flexbox, Grid, and responsive design.
1. Start with semantic HTML and normal flow
Normal flow is the browser’s default arrangement: block elements follow one another in the block direction, and inline content wraps along a line. It gives the page a meaningful reading order before you add visual layout rules. MDN recommends beginning with a well-structured document that remains readable in normal flow.
Use elements for their meaning: header for introductory or site content, nav for navigation, main for the primary content, aside for complementary content, and footer for closing information. A basic shell might look like this:
<body>
<div class="site-layout">
<header class="site-header">
<a class="brand" href="/">Fieldnotes</a>
<nav aria-label="Main navigation">
<ul class="nav-list">
<li><a href="/articles">Articles</a></li>
<li><a href="/guides">Guides</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main class="main-content">
<h1>Recent articles</h1>
<p>Practical notes on building for the web.</p>
<section class="article-grid" aria-label="Articles">
<article class="article-card">
<h2>A useful article</h2>
<p>A short summary of the article content.</p>
<a href="/articles/useful">Read article</a>
</article>
<article class="article-card">
<h2>Another article</h2>
<p>A second summary with a similar purpose.</p>
<a href="/articles/another">Read article</a>
</article>
</section>
</main>
<aside class="sidebar" aria-labelledby="sidebar-title">
<h2 id="sidebar-title">Explore</h2>
<p>Related topics and useful links.</p>
</aside>
<footer class="site-footer">
<p>Fieldnotes</p>
</footer>
</div>
</body>
Before styling, check that headings, links, and content make sense in source order. That order is important when the layout collapses to one column, when styles fail to load, and for people navigating with assistive technology. Keep the visual arrangement consistent with a sensible reading and keyboard order.
2. Build a responsive page shell with CSS Grid
A page shell usually has relationships in two dimensions: the header spans the page, main content sits beside an optional sidebar, and the footer closes the full width. Grid makes those relationships explicit with named areas. Start with one column so the layout works in a narrow space, then introduce a sidebar when the content has room.
* {
box-sizing: border-box;
}
body {
margin: 0;
color: #202124;
font: 1rem/1.5 system-ui, sans-serif;
}
.site-layout {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
gap: 1.5rem;
max-width: 72rem;
margin-inline: auto;
padding: 1rem;
}
.site-header { grid-area: header; }
.main-content { grid-area: main; min-width: 0; }
.sidebar { grid-area: sidebar; }
.site-footer { grid-area: footer; }
@media (min-width: 52rem) {
.site-layout {
grid-template-columns: minmax(0, 1fr) 16rem;
grid-template-areas:
"header header"
"main sidebar"
"footer footer";
gap: 2rem;
}
}
The breakpoint is a design decision, not a universal device threshold. Resize the page and find where the main column or sidebar becomes cramped; choose a width that allows both to remain readable. The example uses 52rem only to demonstrate the change. If your sidebar is longer, your main column narrower, or your spacing larger, the content may need a different breakpoint.
Why use minmax(0, 1fr)?
A grid track sized only with 1fr can still be influenced by a child’s minimum content size. minmax(0, 1fr) allows the track to shrink to zero when needed, while min-width: 0 lets the main grid item shrink inside its track. For long URLs, code, or other unbreakable content, also choose an appropriate wrapping rule, such as overflow-wrap: anywhere, instead of letting it force horizontal overflow.
3. Use Flexbox for navigation and component internals
Flexbox arranges items along one axis at a time: a row or a column. That makes it a good fit for a row of navigation links or for stacking a card’s title, description, and action. It can wrap or distribute space as items change, while Grid is often clearer when both rows and columns define the structure.
.site-header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.nav-list {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem 1.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.nav-list a {
display: inline-block;
padding-block: 0.4rem;
}
.article-card {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
padding: 1.25rem;
border: 1px solid #d7dbe0;
border-radius: 0.75rem;
}
.article-card h2,
.article-card p {
margin: 0;
}
.article-card a {
margin-top: auto;
}
In the card, the column direction stacks content and margin-top: auto can push the link toward the bottom when cards share a row and have different text lengths. That does not force every card to have identical height; it distributes free space within each card. Use a consistent minimum height only if the design actually needs one.
4. Make a card listing adapt with Grid auto-placement
Repeated cards usually benefit from a two-dimensional layout. Grid can create as many columns as fit using a flexible minimum track size. The browser auto-places each card into the next available grid cell, so you do not need to assign every card a row and column.
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
.article-card {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
padding: 1.25rem;
border: 1px solid #d7dbe0;
border-radius: 0.75rem;
}
.article-card h2,
.article-card p { margin: 0; }
.article-card a { margin-top: auto; }
minmax(min(100%, 16rem), 1fr) asks for tracks that are at least about 16rem when space permits, but no wider than the grid container’s full width on a very narrow screen. The available width determines how many columns fit. Change the minimum based on the amount of content each card needs: a card with a wide chart or long title may need more room.
auto-fit or auto-fill?
Both can create repeated tracks based on the available space. With auto-fit, empty tracks collapse so existing cards can expand into the available room. With auto-fill, the browser keeps the empty tracks, which can leave unused-looking space if there are only a few cards. Choose based on the intended behavior when a row is not full.
5. Choose Grid or Flexbox by the relationship
| Question | Grid is a good fit when… | Flexbox is a good fit when… |
|---|---|---|
| How many dimensions define the arrangement? | Both rows and columns matter together. | One row or one column is the main relationship. |
| Are the items a collection? | Items should occupy tracks in a repeated two-dimensional grid. | Items should flow along an axis and distribute or wrap as needed. |
| How explicit should placement be? | Named areas or track relationships make the page easier to understand. | Items can be ordered along a single axis without a page-wide map. |
| Can both methods help? | Yes. Use Grid for the outer shell and card collection, with Flexbox inside navigation and cards. | |
Neither method is universally better or faster. Match the layout tool to how the content relates, then check how it responds when text, viewport size, or the number of items changes.
6. Set responsive behavior around content fit
A responsive layout adapts to the available space. A reliable starting approach is a simple, narrow arrangement, followed by columns or alternate area arrangements once the content can support them. A breakpoint should solve a visible content problem: a navigation row wraps awkwardly, cards become too narrow, or a sidebar squeezes the main text.
- Begin with the narrow layout. Keep the content in a clear, readable order and avoid unnecessary fixed widths.
- Resize gradually. Watch the actual content rather than switching only at familiar phone or tablet sizes.
- Add a breakpoint when needed. Introduce columns when the main content and sidebar can both keep useful widths.
- Check edge cases. Try long headings, translated labels, fewer cards, many cards, and browser zoom.
Grid and Flexbox often handle changing widths without many breakpoints. Use media queries for genuine structural changes, such as moving a sidebar beside the main content or changing navigation alignment. Avoid breakpoints that add complexity but do not improve fit or reading.
7. Use positioning and floats for their specific jobs
Positioning changes how an element participates in layout. Absolutely and fixed positioned boxes are removed from normal flow, so surrounding content does not reserve their ordinary space. That is useful for intentional layering or viewport-attached UI, but it makes positioning a poor general replacement for Grid or Flexbox page structure.
.card {
position: relative;
}
.card-badge {
position: absolute;
inset-block-start: 0.75rem;
inset-inline-end: 0.75rem;
}
.help-button {
position: fixed;
inset-inline-end: 1rem;
inset-block-end: 1rem;
}
The badge is placed relative to its positioned card; it does not take up space in the card’s ordinary flow. The fixed button is positioned against the viewport and also does not reserve room in the document. Make sure overlays do not cover important content or controls, especially at narrow widths and high zoom.
Floats have a narrower current role: they allow text to wrap alongside an item, such as an image in an article. They are not the default tool for a multi-column shell.
.article-image {
float: inline-start;
max-inline-size: min(40%, 18rem);
margin-inline-end: 1rem;
margin-block-end: 0.75rem;
}
.article-copy::after {
content: "";
display: block;
clear: both;
}
If later content should start below the floated image, clear the float as needed. Use Grid or Flexbox when the goal is to create columns for the page or arrange a collection of independent items.
8. A compact complete example
These styles bring the patterns together. Apply the HTML from the first section, then use this stylesheet:
* { box-sizing: border-box; }
body {
margin: 0;
color: #202124;
font: 1rem/1.5 system-ui, sans-serif;
}
a { color: #155eef; }
.site-layout {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-areas: "header" "main" "sidebar" "footer";
gap: 1.5rem;
max-width: 72rem;
margin-inline: auto;
padding: 1rem;
}
.site-header {
grid-area: header;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.brand { font-weight: 700; }
.nav-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.main-content { grid-area: main; min-width: 0; }
.sidebar { grid-area: sidebar; }
.site-footer { grid-area: footer; }
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
.article-card {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
padding: 1.25rem;
border: 1px solid #d7dbe0;
border-radius: 0.75rem;
}
.article-card h2,
.article-card p { margin: 0; }
.article-card a { margin-top: auto; }
@media (min-width: 52rem) {
.site-layout {
grid-template-columns: minmax(0, 1fr) 16rem;
grid-template-areas:
"header header"
"main sidebar"
"footer footer";
gap: 2rem;
}
}
The example intentionally keeps page regions in the same source order as the narrow layout. If you change the visual order with Grid, confirm that the reading and keyboard order still make sense; visual rearrangement does not rewrite the document.
9. Inspect the rendered layout
A browser viewport is the final place to judge wrapping, overflow, and whether a breakpoint is solving a real problem. Inspect a narrow width, a wide width, and at least one width near each breakpoint. Check long content, empty or short card rows, and whether controls remain visible. Browser developer tools can help identify which grid track or flex item is constraining the page.
For repeatable screenshots in a review workflow, ScreenshotNeo is a website screenshot API and MCP server. You can capture a rendered page as PNG, JPEG, WebP, or PDF with a single GET request, and use screenshots to compare the same layout at chosen viewport sizes.
10. Troubleshooting common layout problems
| Symptom | Likely cause | Fix |
|---|---|---|
| The page scrolls horizontally. | A grid child’s min-content width, long unbroken text, or a fixed-width element exceeds its track. | Use minmax(0, 1fr) for a shrinkable track, min-width: 0 on the grid item, and suitable wrapping such as overflow-wrap: anywhere. |
| The sidebar does not appear in the intended place. | A named area is missing, misspelled, or its row strings do not form a valid grid map. | Check that every grid-area name matches a name in grid-template-areas and that each row has the same number of tracks. |
| Cards are too narrow on mobile. | The repeated track has a minimum wider than its container, or another rule imposes a fixed width. | Use a minimum bounded by the container, as in minmax(min(100%, 16rem), 1fr), and remove conflicting fixed widths. |
| Flex items overflow instead of shrinking. | A flex item’s default minimum size or long content prevents it from shrinking. | Try min-width: 0 on the constrained child and define how long content wraps or scrolls. |
| Navigation wraps in an unattractive place. | There is not enough width for the chosen labels, gaps, and brand. | Allow a deliberate wrap, reduce spacing, or switch alignment at a content-driven breakpoint. Do not make labels unreadably small to preserve one row. |
| An overlay covers text or buttons. | An absolutely or fixed positioned element is out of flow and does not push content away. | Reserve space in the layout, reposition the overlay at narrow sizes, or remove it where it obstructs content. |
| Text no longer wraps beside a floated image. | The float was cleared, constrained, or does not fit alongside the text column. | Check float direction, width, margins, and clearing; at narrow widths let the image return to normal flow. |
11. Performance, reliability, and maintenance
Choose Grid and Flexbox for the layout relationships they express. The provided sources establish their layout roles, not a universal speed ranking, so there is no supported basis here for claiming one is always faster. For maintainability, keep the page structure semantic, use a small number of content-driven breakpoints, and avoid using positioning to simulate normal columns.
For reliable visual review, capture consistent viewport dimensions and repeat the same checks after meaningful layout changes. A screenshot can reveal clipping or unexpected wrapping, but it does not replace checking keyboard navigation, zoom, text resizing, or the page’s semantic reading order. Screenshot cost depends on the tool and plan; no third-party pricing comparison is established in the research for this CSS topic.
12. Or skip the browser setup
If you want a rendered page capture without wiring up a browser, ScreenshotNeo accepts a URL and returns an image or PDF. See the ScreenshotNeo API documentation for the available parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
13. FAQ
Can I use Grid and Flexbox on the same page?
Yes. A common arrangement uses Grid to define page regions and repeated card tracks, then Flexbox for navigation and the contents of individual cards.
Should every layout have a sidebar?
No. Treat the sidebar as optional content. If it makes the main column too narrow or does not help the page, leave it out.
Should I use absolute positioning to make columns?
Usually not. Absolute positioning removes an element from normal flow, so it does not make room for surrounding content. Use Grid or Flexbox for columns.
Are breakpoint widths tied to specific devices?
They can be chosen from the content’s needs. Pick a breakpoint where the current arrangement stops fitting comfortably rather than assuming every device of a named type has the same width.


