For most of web development's history, building layouts in CSS was genuinely painful. Developers used floats, negative margins, clearfix hacks and endless media query workarounds just to put a sidebar next to some content. Even simple things were complicated.
That era is over. Modern CSS has powerful layout tools built right in. Things that used to require JavaScript frameworks or hacky workarounds are now one or two lines of CSS. Layouts that used to take hours now take minutes.
This guide walks through the most important modern CSS layout techniques — Flexbox, CSS Grid, container queries, nesting, logical properties and more — all explained from scratch in simple English so you can start using them right away.
Flexbox — One Dimension at a Time
Flexbox is for laying out items in a single direction — either in a row (horizontally) or in a column (vertically). You turn a container into a flex container and its direct children become flex items that can grow, shrink and align themselves automatically.
Think of Flexbox like a shelf. You put things on the shelf and tell them how to space themselves out, whether they should all be the same height, and how to line up. Everything goes in one direction along that shelf.
Core Concepts You Actually Need
.container {
display: flex;
/* Direction — row (left to right) or column (top to bottom) */
flex-direction: row; /* default */
flex-direction: column;
flex-direction: row-reverse;
/* Align along the MAIN axis (direction) */
justify-content: flex-start; /* pack at the start */
justify-content: center; /* centre everything */
justify-content: space-between; /* first and last at edges, equal gaps */
justify-content: space-evenly; /* equal gaps everywhere including edges */
/* Align along the CROSS axis (perpendicular to direction) */
align-items: stretch; /* items fill the container height (default) */
align-items: center; /* vertically centre items in the container */
align-items: flex-start; /* align items to the top */
align-items: baseline; /* align text baselines */
/* Allow wrapping onto multiple lines */
flex-wrap: wrap;
gap: 16px; /* space between items (row gap and column gap) */
}
/* On individual items */
.item {
/* flex: grow shrink basis — shorthand for all three */
flex: 1; /* grow to fill available space equally */
flex: 0 0 200px; /* fixed 200px, do not grow or shrink */
flex: 2; /* grow twice as fast as a flex: 1 neighbour */
/* Override align-items for just this one item */
align-self: flex-end;
}
Real World Flexbox Patterns
/* 1. Perfect centring — both horizontally and vertically */
.centre-everything {
display: flex;
align-items: center;
justify-content: center;
}
/* 2. Nav with logo on left and links on the right */
.nav {
display: flex;
align-items: center;
justify-content: space-between;
}
/* 3. Cards that wrap and fill the row evenly */
.card-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card-row .card {
flex: 1 1 280px; /* grow and shrink, but never below 280px */
}
/* 4. Sticky footer — main grows to push footer down */
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main { flex: 1; } /* grows to fill remaining space */
/* 5. Push one item to the far end */
.toolbar { display: flex; align-items: center; gap: 12px; }
.toolbar .push-right { margin-left: auto; } /* auto margin eats all free space */
CSS Grid — Two Dimensions at Once
CSS Grid lets you control layout in both rows and columns at the same time. It is like a spreadsheet for your web page. You define the columns and rows, then place items wherever you want in that grid.
Flexbox is great for one row of items or one column. Grid is great for the overall page structure, magazine-style layouts, card grids and anything where items need to align across both dimensions.
How Grid Works
.grid {
display: grid;
/* Define columns — 3 equal columns */
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: repeat(3, 1fr); /* same thing, shorter */
/* Mixed columns — sidebar + main content */
grid-template-columns: 240px 1fr;
grid-template-columns: 200px 1fr 300px; /* sidebar, main, aside */
/* Rows */
grid-template-rows: 60px 1fr 80px; /* header height, main grows, footer height */
/* Gap between cells */
gap: 24px;
column-gap: 20px;
row-gap: 16px;
}
/* Place an item in the grid manually */
.header {
/* span from column line 1 to line 4 (takes all 3 columns) */
grid-column: 1 / 4;
grid-column: 1 / -1; /* -1 means the last line — easier */
}
.sidebar {
grid-row: 2 / 4; /* spans 2 rows tall */
}
/* The fr unit means "fraction of available space" */
/* 1fr 2fr 1fr means the middle column gets twice the space */
Template Areas — Name Your Layout Zones
Template areas let you draw your layout visually using names. Instead of thinking about column and row numbers, you literally write out the layout shape with words. This is one of the most readable features in all of CSS.
.page {
display: grid;
grid-template-columns: 220px 1fr 260px;
grid-template-rows: 64px 1fr 80px;
min-height: 100vh;
gap: 0;
/* Draw your layout — each string is a row, each word is a cell */
/* A dot means that cell is empty */
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
}
/* Each element just says which area it belongs to */
header { grid-area: header; }
.sidebar{ grid-area: sidebar; }
main { grid-area: main; }
aside { grid-area: aside; }
footer { grid-area: footer; }
/* Responsive — collapse to single column on small screens */
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"aside"
"footer";
}
}
auto-fill and auto-fit — Responsive Grids Without Media Queries
One of the most powerful Grid tricks is creating a fully responsive card grid that adjusts the number of columns automatically — without writing a single media query. You just tell CSS the minimum card width and it figures out how many columns fit.
/* auto-fill — creates as many columns as will fit at the minimum size */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
/* On a 900px wide screen this creates 3 columns (3 x 280px = 840px) */
/* On a 600px screen it becomes 2 columns */
/* On a 300px screen it becomes 1 column */
/* All automatic. No media queries needed. */
/* auto-fill vs auto-fit */
/* auto-fill: keeps empty columns if not enough items to fill the row */
/* auto-fit: collapses empty columns so items stretch to fill the space */
.card-grid-fit {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
Subgrid — Children That Inherit the Parent Grid
Subgrid solves a long-standing problem: getting the children inside a grid item to align with the parent grid. Before subgrid, card titles inside a grid would not line up with each other because each card was its own separate layout context. With subgrid, a child can say "I want to use my parent's grid lines."
/* Parent grid */
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto;
}
/* Each card spans 1 column but 4 rows (image, tag, title, body) */
.card {
grid-row: span 4;
display: grid;
/* subgrid tells the card to use the PARENT's row definitions */
/* Now the image, tag, title and body line up perfectly across all 3 cards */
grid-template-rows: subgrid;
}
/* Without subgrid, the title in a tall card would be pushed down */
/* With subgrid, titles across all cards sit at exactly the same height */
Grid vs Flexbox — When to Use Which
Both tools are excellent and they work together. The simple rule is:
- Use Flexbox when you have items in a single line — a navigation bar, a row of buttons, a list of tags, one row of cards
- Use Grid when you need to control both rows and columns — a page layout, a card gallery, a form with multiple columns
| Situation | Best Choice | Why |
| Navigation bar with logo and links | Flexbox | Single row, items need to align and space out |
| Page layout with header, sidebar, main, footer | Grid | Two dimensional, named areas are perfect here |
| Row of tags or chips | Flexbox with wrap | Items can flow onto the next line naturally |
| Responsive card gallery | Grid auto-fill | Columns adjust to container width automatically |
| Centring one item in a box | Either | Both work equally well with align-items and justify-content |
| Card with image, title and button lined up | Grid subgrid | Aligns content across sibling cards perfectly |
Container Queries — Respond to the Parent, Not the Viewport
Media queries let you change styles based on the viewport width (the whole browser window). But what if you have a card component that might appear in a narrow sidebar or a wide main section? The card needs to look different in each context, but the viewport width is the same in both cases.
Container queries solve this. Instead of asking "how wide is the screen", they ask "how wide is my container?" This makes components truly reusable — each one responds to its own available space.
/* Step 1: Tell the parent it is a container to measure */
.card-wrapper {
container-type: inline-size; /* measure the inline (horizontal) size */
container-name: card; /* optional name for targeting */
}
/* Step 2: Style the card based on its container's width */
.card {
display: flex;
flex-direction: column;
}
/* When the CONTAINER is wider than 500px, switch to horizontal layout */
@container (min-width: 500px) {
.card {
flex-direction: row;
}
.card .card__image {
width: 200px;
flex-shrink: 0;
}
}
/* The same card component now works perfectly in:
- a narrow sidebar (stacks vertically)
- a wide main section (goes horizontal)
Without any JavaScript and without caring about viewport width */
✅
Container queries are now supported in all modern browsers. They are one of the most useful additions to CSS in years. If you build reusable components, start using them today.
CSS Nesting — Write Less, Stay Organised
CSS nesting is now built into the browser. You can write child selectors inside their parent selector, just like in SCSS or Less — but without any build tools. Your CSS structure mirrors your HTML structure, which makes it much easier to read and maintain.
/* Old way — repetitive, easy to get out of sync */
.card { background: #1a1b26; }
.card__title { font-size: 1.1rem; }
.card__body { color: #9395a5; }
.card__footer { padding-top: 12px; }
.card:hover { border-color: #ffb800; }
.card.featured { border-color: #ff6b35; }
/* New way — nested, everything in one place */
.card {
background: #1a1b26;
border: 1px solid #23253a;
/* & refers to the parent selector (.card) */
&:hover {
border-color: #ffb800;
}
&.featured {
border-color: #ff6b35;
}
.card__title {
font-size: 1.1rem;
font-weight: 600;
}
.card__body {
color: #9395a5;
line-height: 1.6;
}
.card__footer {
padding-top: 12px;
border-top: 1px solid #23253a;
}
}
Logical Properties — CSS That Works in All Languages
Traditional CSS uses physical directions: top, right, bottom, left. But not every language reads left to right. Arabic reads right to left. Some languages read top to bottom. Physical properties break completely in these contexts.
Logical properties replace physical directions with logical ones: start, end, inline, block. In a left-to-right language, inline-start means left. In a right-to-left language, it means right — automatically, with no extra code.
/* Physical — breaks in RTL languages */
.old {
margin-left: 16px;
padding-right: 12px;
border-left: 3px solid gold;
text-align: left;
width: 300px;
height: 200px;
}
/* Logical — adapts to writing direction automatically */
.new {
margin-inline-start: 16px; /* left in LTR, right in RTL */
padding-inline-end: 12px; /* right in LTR, left in RTL */
border-inline-start: 3px solid gold;
text-align: start; /* left in LTR, right in RTL */
inline-size: 300px; /* width in horizontal writing mode */
block-size: 200px; /* height in horizontal writing mode */
}
/* Shorthands for margin and padding */
.shorthand {
margin-inline: auto; /* horizontal margin (centres a block) */
margin-block: 16px; /* vertical margin */
padding-inline: 24px; /* horizontal padding */
padding-block: 12px; /* vertical padding */
}
clamp and Fluid Typography
The clamp() function lets you define a value that grows smoothly between a minimum and maximum based on the viewport or container width. No media queries needed. The font just gets naturally bigger as the screen gets wider and never goes below a safe minimum or above a readable maximum.
/* clamp(minimum, ideal, maximum) */
/* Font size that grows from 1rem to 2rem as viewport widens */
h1 {
font-size: clamp(1.5rem, 4vw, 3rem);
/* min: 1.5rem ideal: 4% of viewport max: 3rem */
}
p {
font-size: clamp(0.875rem, 1.5vw, 1.125rem);
}
/* Fluid spacing — padding that grows with the screen */
.section {
padding: clamp(24px, 5vw, 80px);
max-width: clamp(300px, 90%, 1200px);
}
/* You can also use min() and max() */
.sidebar {
width: min(300px, 30%); /* whichever is smaller */
}
.hero {
min-height: max(400px, 50vh); /* whichever is larger */
}
The has Selector — Style a Parent Based on Its Children
For years, CSS could only style children based on what was happening with their parent. The :has() selector reverses this. It lets you style a parent based on what children it contains. This was previously impossible without JavaScript.
/* Style a form that contains an invalid input */
form:has(input:invalid) {
border-color: #f87171;
background: rgba(248,113,113,0.05);
}
/* Make a card taller when it contains an image */
.card:has(img) {
min-height: 320px;
}
/* Show nav links differently when nav has a dropdown open */
nav:has(.dropdown:hover) .nav-link:not(.dropdown .nav-link) {
opacity: 0.5;
}
/* Switch page layout when sidebar is present */
.page:has(.sidebar) main {
grid-template-columns: 240px 1fr;
}
.page:not(:has(.sidebar)) main {
grid-template-columns: 1fr;
}
ℹ️
:has() is now supported in all modern browsers including Chrome, Firefox, Safari and Edge. It is one of the most powerful new selectors CSS has ever received.
Cascade Layers — Take Control of Specificity
Specificity wars are when two CSS rules fight to control the same element and you keep adding more selectors or !important just to win. Cascade layers end this forever by letting you declare an order of priority upfront.
/* Define the order — later layers win over earlier ones */
@layer reset, base, components, utilities;
/* Reset styles — lowest priority */
@layer reset {
* { box-sizing: border-box; margin: 0; }
}
/* Base typography and colours */
@layer base {
body { font-family: sans-serif; }
a { color: inherit; }
}
/* Component styles */
@layer components {
.btn { padding: 8px 16px; background: #ffb800; }
}
/* Utilities — highest priority, always win */
@layer utilities {
.hidden { display: none; }
.text-center { text-align: center; }
}
/* Now .hidden always beats .btn because utilities layer beats components */
/* No !important needed, no specificity tricks — just layer order */
Quick Reference Table
| Feature | What It Does | Browser Support |
| Flexbox | One direction layout — row or column | All browsers |
| CSS Grid | Two direction layout — rows and columns | All browsers |
| Grid auto-fill/fit | Responsive columns without media queries | All browsers |
| Grid subgrid | Children that inherit parent grid lines | All modern browsers |
| Container queries | Respond to container size not viewport | All modern browsers |
| CSS nesting | Nest selectors like SCSS, no build tools | All modern browsers |
| Logical properties | margin-inline, padding-block, inline-size | All modern browsers |
| clamp() | Fluid sizes between a min and max | All browsers |
| :has() selector | Style parent based on its children | All modern browsers |
| Cascade layers | Explicit control over style priority | All modern browsers |
⚡ Key Takeaways
- Use Flexbox for one-dimensional layouts (a row of buttons, a nav bar, a list). Use Grid for two-dimensional layouts (page structure, card galleries).
- grid-template-areas lets you draw your layout with words. It is the most readable way to describe a page structure and makes responsive changes trivially easy.
- repeat(auto-fill, minmax(280px, 1fr)) creates a fully responsive card grid that adjusts column count automatically — with zero media queries.
- Subgrid lets child elements align to the parent grid. Use it when you need titles or images inside cards to line up perfectly across a whole row.
- Container queries (@container) style components based on their parent's width, not the viewport. This makes components truly reusable in different contexts.
- CSS nesting is now native in all modern browsers. You can write child selectors inside parent selectors without SCSS or any build tool.
- Logical properties (margin-inline, padding-block, inline-size) replace left/right/width/height with direction-agnostic terms that work correctly in all writing systems.
- clamp(min, ideal, max) creates fluid typography and spacing that grows smoothly with the viewport — no stepped media query breakpoints needed.
- :has() lets you style a parent based on what children it contains. This was previously impossible in CSS alone and required JavaScript.
- @layer solves specificity wars by declaring explicit priority order for your style groups. Utilities always win. Resets always lose. No !important ever needed.
Tags:
CSS
CSS Grid
Flexbox
Frontend
Layout
Intermediate
Shashank Shekhar
Founder & Creator — Hoopsiper.com
Full stack developer and educator. Building Hoopsiper to help developers learn faster through practical, no-fluff coding guides on JavaScript, AI/ML, Python and modern web development.