CSS Layout Engineering: The Complete Guide to Flexbox, Grid, and Modern Positioning
Your blog from Module 2 looks professional. Clean typography, a cohesive color system, readable spacing. But it's still a single column stacked from top to bottom. The navigation is horizontal, sure, but the content area is just a centered blob. No sidebar. No card grids. No sticky behavior that stays put while you scroll.
This is where most developers get stuck — not because layout is hard, but because they treat Flexbox and Grid as interchangeable magic spells instead of understanding the problems each one solves.
By the end of this tutorial, you'll rebuild your blog with:
- A CSS Grid page architecture (header, main content, sidebar, footer)
- Flexbox inside components (navigation, card footers, form rows)
- A sticky header that stays visible while scrolling
- Container Queries so sidebar components adapt to their own width, not the screen
- Logical properties for future-proof internationalization
Open your my-blog folder. We're not starting over — we're restructuring.
Part 1: The display Property — From Inline to Layout Engine
Before Flexbox and Grid existed, CSS layout was a hack. Floats for columns. Tables for alignment. Clearfix hacks everywhere. Modern display values changed everything.
The Evolution
1/* The old world */ 2span { display: inline; } /* Flows with text, ignores width/height */ 3div { display: block; } /* Full width, stacks vertically */ 4img { display: inline-block; } /* Inline flow, but respects box model */ 5 6/* The modern world */ 7nav { display: flex; } /* One-dimensional layout engine */ 8main { display: grid; } /* Two-dimensional layout engine */ 9.dialog { display: none; } /* Removed from layout entirely */
What Each Value Actually Does
| Value | Behavior | Use Case |
|---|---|---|
block | Takes full width, starts on new line | Paragraphs, sections, containers |
inline | Flows with text, ignores width/height | Links, spans, emphasis |
inline-block | Inline flow + box model respect | Buttons in a row, image galleries |
none | Removed from document flow | Hidden dialogs, toggled sections |
flex | One-dimensional layout (row or column) | Navigation, card footers, form rows |
grid | Two-dimensional layout (rows + columns) | Page architecture, dashboards, galleries |
Key Insight:
flexandgriddon't just change how children sit — they activate entirely new layout algorithms with their own rules for sizing, alignment, and spacing.
Part 2: Positioning — The Layered Dimension
Positioning moves elements out of normal document flow. But most developers break layouts because they don't understand stacking contexts — the 3D layer system that decides what appears on top.
The Five Position Values
1/* static — the default. Follows normal flow. */ 2div { position: static; } 3 4/* relative — positioned relative to its normal spot */ 5.badge { 6 position: relative; 7 top: -5px; /* Moves up 5px from where it would be */ 8 left: 10px; /* Moves right 10px */ 9} 10 11/* absolute — positioned relative to nearest positioned ancestor */ 12.tooltip { 13 position: absolute; 14 top: 100%; 15 left: 50%; 16 transform: translateX(-50%); 17} 18 19/* fixed — positioned relative to the viewport */ 20.back-to-top { 21 position: fixed; 22 bottom: 2rem; 23 right: 2rem; 24} 25 26/* sticky — hybrid: relative until scroll threshold, then fixed */ 27header { 28 position: sticky; 29 top: 0; 30}
The Positioning Reference System
| Value | Positioned Relative To | Removed from Flow? |
|---|---|---|
relative | Its original position in normal flow | No (space is reserved) |
absolute | Nearest position: relative/absolute/fixed/sticky ancestor | Yes (space collapses) |
fixed | The viewport | Yes |
sticky | Its container + scroll position | No (until threshold hit) |
Practical Example: Sticky Header for the Blog
1header { 2 position: sticky; 3 top: 0; 4 z-index: 100; 5 background: var(--color-surface); 6 /* The header stays at top: 0 when you scroll past it */ 7}
Understanding z-index and Stacking Contexts
z-index only works on positioned elements (relative, absolute, fixed, sticky) or flex/grid children. But here's the trap: a new stacking context is created by more than just z-index.
1/* These ALL create new stacking contexts */ 2.context-1 { position: relative; z-index: 1; } 3.context-2 { opacity: 0.99; } /* Less than 1 */ 4.context-3 { transform: translateX(0); } /* Any transform */ 5.context-4 { filter: blur(0); } /* Any filter */ 6.context-5 { isolation: isolate; } /* Explicitly */
The Rule: A child with z-index: 9999 can NEVER escape its parent's stacking context. If the parent is z-index: 1, the child is trapped inside that layer.
1/* This will NOT work as expected */ 2.parent { 3 position: relative; 4 z-index: 1; /* Creates stacking context */ 5} 6 7.child { 8 position: absolute; 9 z-index: 9999; /* Still trapped inside parent's layer */ 10} 11 12/* The fix: raise the parent's z-index, or remove the context */ 13.parent { 14 position: relative; 15 /* No z-index = no new stacking context */ 16}
Debugging Tip: In Chrome DevTools, open the Layers panel (3-dot menu → More Tools → Layers). It visualizes stacking contexts as 3D layers. If your modal is behind an overlay, trace the stacking context chain upward.
Part 3: Flexbox — One-Dimensional Distribution
Flexbox solves one problem perfectly: distributing items along a single axis (row or column). Navigation bars. Card footers. Centering a div vertically. Form rows with labels and inputs.
The Flexbox Model
┌─────────────────────────────────────────┐
│ flex-container (display: flex) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │item1│ │item2│ │item3│ │item4│ ← main axis
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ ↑ cross axis │
└─────────────────────────────────────────┘
Essential Properties
1/* CONTAINER properties */ 2nav { 3 display: flex; 4 flex-direction: row; /* row | row-reverse | column | column-reverse */ 5 justify-content: space-between; /* main axis alignment */ 6 align-items: center; /* cross axis alignment */ 7 flex-wrap: wrap; /* allow wrapping to next line */ 8 gap: 1.5rem; /* spacing between items (replaces margins) */ 9} 10 11/* ITEM properties */ 12nav a { 13 flex-grow: 1; /* Grow to fill space (0 = don't grow) */ 14 flex-shrink: 0; /* Shrink if needed (0 = don't shrink) */ 15 flex-basis: auto; /* Starting size before grow/shrink */ 16 /* Shorthand: flex: 1 0 auto; */ 17 align-self: flex-end; /* Override container's align-items */ 18}
The justify-content and align-items Cheat Sheet
| Property | flex-direction: row | flex-direction: column |
|---|---|---|
justify-content | Horizontal alignment | Vertical alignment |
align-items | Vertical alignment | Horizontal alignment |
1/* Center a div both ways (the famous problem) */ 2.center-me { 3 display: flex; 4 justify-content: center; /* Horizontal center */ 5 align-items: center; /* Vertical center */ 6 min-height: 100vh; 7}
Practical: Blog Navigation with Flexbox
1header { 2 display: flex; 3 flex-wrap: wrap; 4 align-items: baseline; 5 gap: 0.5rem 2rem; 6 padding: 1rem 2rem; 7} 8 9header h1 { 10 /* Takes available space, pushes nav to the right */ 11 flex: 1; 12 margin: 0; 13} 14 15header nav ul { 16 display: flex; 17 gap: 1.5rem; 18 list-style: none; 19 padding: 0; 20 margin: 0; 21} 22 23/* On small screens, stack vertically */ 24@media (max-width: 600px) { 25 header { 26 flex-direction: column; 27 align-items: flex-start; 28 } 29 30 header h1 { 31 flex: none; /* Stop pushing */ 32 } 33}
Practical: Card Footer with Flexbox
1.card-footer { 2 display: flex; 3 justify-content: space-between; 4 align-items: center; 5 gap: 1rem; 6 padding-top: 1rem; 7 border-top: 1px solid var(--color-border); 8} 9 10.card-footer .date { 11 color: var(--color-text-muted); 12 font-size: 0.875rem; 13} 14 15.card-footer .actions { 16 display: flex; 17 gap: 0.5rem; 18}
The gap Property: The Best Thing Since border-box
Before gap, you used margins on children and negative margins on parents. It was brittle.
1/* Old way (painful) */ 2.nav-item { 3 margin-right: 1.5rem; 4} 5.nav-item:last-child { 6 margin-right: 0; 7} 8 9/* New way (clean) */ 10nav ul { 11 display: flex; 12 gap: 1.5rem; /* Space between items. No margin hacks. */ 13}
gap works in both Flexbox and Grid. It only creates space between items, not around them.
Part 4: CSS Grid — Two-Dimensional Architecture
Grid is for page layouts. Dashboards. Photo galleries. Anywhere you need to control both rows and columns simultaneously.
Defining the Grid
1.page-layout { 2 display: grid; 3 /* 3 columns: sidebar (250px), main (flexible), sidebar (250px) */ 4 grid-template-columns: 250px 1fr 250px; 5 6 /* 2 rows: header (auto height), content (fills remaining) */ 7 grid-template-rows: auto 1fr; 8 9 gap: 2rem; 10 min-height: 100vh; 11}
The fr Unit: Fractional Space
1fr means "one fraction of the remaining space." It's like flex-grow for Grid.
1.grid { 2 display: grid; 3 grid-template-columns: 1fr 2fr 1fr; 4 /* Column 2 is twice as wide as columns 1 and 3 */ 5} 6 7.grid { 8 display: grid; 9 grid-template-columns: 200px 1fr; 10 /* Sidebar is fixed 200px, main content fills the rest */ 11}
minmax(), repeat(), and Responsive Grids
1/* Auto-fill: create as many 250px columns as fit */ 2.card-grid { 3 display: grid; 4 grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 5 gap: 1.5rem; 6} 7 8/* Auto-fit: same, but collapse empty tracks */ 9.card-grid { 10 display: grid; 11 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); 12 gap: 1.5rem; 13}
auto-fill vs auto-fit:
auto-fill— creates empty tracks if there's extra spaceauto-fit— stretches items to fill the row, no empty tracks
This single line replaces media queries for card grids. No more
@media (min-width: 768px) { .card { width: 50%; } }. The grid adapts automatically.
Named Grid Areas: Layout as a Map
1.page-layout { 2 display: grid; 3 grid-template-columns: 280px 1fr; 4 grid-template-rows: auto 1fr auto; 5 grid-template-areas: 6 "header header" 7 "sidebar main" 8 "footer footer"; 9 gap: 0 2rem; 10 min-height: 100vh; 11} 12 13.page-layout > header { grid-area: header; } 14.page-layout > aside { grid-area: sidebar; } 15.page-layout > main { grid-area: main; } 16.page-layout > footer { grid-area: footer; }
This is readable. You can see the layout in the CSS. Change the grid-template-areas string and the entire page restructures — no touching individual elements.
Grid Alignment (Just Like Flexbox)
1.grid-container { 2 display: grid; 3 place-items: center; /* Shorthand for align + justify */ 4 /* OR */ 5 justify-items: center; /* Horizontal alignment of all grid items */ 6 align-items: center; /* Vertical alignment of all grid items */ 7 8 justify-content: center; /* Alignment of the grid itself in the container */ 9 align-content: center; 10}
Part 5: Flexbox vs. Grid — The Decision Framework
This is the #1 question in CSS layout. Here's the definitive answer.
| Use Flexbox When | Use Grid When |
|---|---|
| Distributing items in a single row or column | Building a full page architecture |
| Aligning a navbar, form row, or card footer | Creating a dashboard, magazine layout, or photo gallery |
| Content size determines the layout | The layout determines the content size |
| You need items to wrap responsively | You need precise control over rows AND columns |
| Centering a single element | Building a complex 2D interface |
They work together. Grid for the page skeleton. Flexbox for the components inside each grid cell.
┌─────────────────────────────────────┐
│ Grid: Page Layout │
│ ┌──────────┬─────────────────────┐ │
│ │ Grid: │ Grid: Main Content │ │
│ │ Sidebar │ ┌───────────────┐ │ │
│ │ (Flex │ │ Flex: Card │ │ │
│ │ for │ │ ┌───┬───────┐ │ │ │
│ │ nav) │ │ │img│ Flex │ │ │ │
│ │ │ │ │ │ for │ │ │ │
│ │ │ │ │ │ text │ │ │ │
│ │ │ │ └───┴───────┘ │ │ │
│ │ │ │ Flex: Card │ │ │
│ │ │ │ Footer │ │ │
│ │ │ └───────────────┘ │ │
│ └──────────┴─────────────────────┘ │
└─────────────────────────────────────┘
Part 6: Container Queries — Responsive Components, Not Viewports
Media queries respond to the viewport width. Container queries respond to the container's width. This is the biggest shift in responsive design since 2010.
Why Container Queries Matter
Your sidebar is 280px wide. Your main content is 600px wide. A card component in the sidebar needs a stacked layout. The same card in main content needs a horizontal layout. Media queries can't help — the viewport is the same. Container queries can.
Setting Up a Container
1/* 1. Define a container */ 2.sidebar, 3.main-content { 4 container-type: inline-size; /* Measure width */ 5 container-name: content; /* Optional name */ 6} 7 8/* 2. Query the container, not the viewport */ 9@container content (min-width: 400px) { 10 .card { 11 display: grid; 12 grid-template-columns: 200px 1fr; 13 gap: 1.5rem; 14 } 15 16 .card img { 17 width: 100%; 18 height: 100%; 19 object-fit: cover; 20 } 21} 22 23@container content (max-width: 399px) { 24 .card { 25 display: flex; 26 flex-direction: column; 27 } 28 29 .card img { 30 width: 100%; 31 height: 180px; 32 object-fit: cover; 33 } 34}
Container Query Units
1@container (min-width: 300px) { 2 .widget { 3 padding: 5cqw; /* 5% of container width */ 4 font-size: 4cqh; /* 4% of container height */ 5 } 6}
| Unit | Meaning |
|---|---|
cqw | 1% of container width |
cqh | 1% of container height |
cqi | 1% of container inline size (writing-mode aware) |
cqb | 1% of container block size |
Browser Support: Container Queries are supported in all modern browsers (Chrome 105+, Firefox 110+, Safari 16+). For older browsers, the
@containerrule is simply ignored — a safe progressive enhancement.
Part 7: Logical Properties — Future-Proof Internationalization
Physical properties (margin-left, border-top) assume left-to-right, top-to-bottom reading. Logical properties adapt to the writing mode.
1/* Physical (LTR only) */ 2.box { 3 margin-left: 1rem; 4 margin-right: 1rem; 5 border-top: 2px solid blue; 6 text-align: left; 7} 8 9/* Logical (works for LTR, RTL, and vertical writing) */ 10.box { 11 margin-inline: 1rem; /* start + end (left + right in LTR) */ 12 margin-inline-start: 1rem; /* "before" in inline direction */ 13 margin-block: 2rem; /* top + bottom */ 14 border-block-start: 2px solid blue; /* "top" in block direction */ 15 text-align: start; /* "left" in LTR, "right" in RTL */ 16}
The Mapping
| Physical | Logical | Description |
|---|---|---|
margin-top | margin-block-start | Start of block axis |
margin-bottom | margin-block-end | End of block axis |
margin-left | margin-inline-start | Start of inline axis |
margin-right | margin-inline-end | End of inline axis |
width | inline-size | Size in inline direction |
height | block-size | Size in block direction |
border-top-left-radius | border-start-start-radius | Logical corner |
When to Use: For any component that might be reused in a multilingual site (Arabic, Hebrew, Japanese). Even if you're building English-only now, logical properties are becoming the standard in modern CSS frameworks.
Part 8: Rebuilding the Blog — The Complete Layout
Here's the full rebuild. We use Grid for page architecture, Flexbox for components, sticky positioning for the header, and Container Queries for sidebar widgets.
Updated styles.css
1/* ============================================ 2 RESET & BASE 3 ============================================ */ 4*, 5*::before, 6*::after { 7 box-sizing: border-box; 8 margin: 0; 9 padding: 0; 10} 11 12:root { 13 /* Colors */ 14 --color-primary: #2980b9; 15 --color-primary-dark: #1f618d; 16 --color-text: #2c3e50; 17 --color-text-muted: #7f8c8d; 18 --color-bg: #f0f2f5; 19 --color-surface: #ffffff; 20 --color-border: #e1e8ed; 21 22 /* Typography */ 23 --font-body: 'Inter', system-ui, -apple-system, sans-serif; 24 --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); 25 --text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem); 26 --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); 27 --text-lg: clamp(1.125rem, 1rem + 0.65vw, 1.35rem); 28 --text-xl: clamp(1.35rem, 1.1rem + 1.2vw, 1.75rem); 29 --text-2xl: clamp(1.75rem, 1.3rem + 2.2vw, 2.5rem); 30 31 /* Spacing */ 32 --space-xs: 0.5rem; 33 --space-sm: 1rem; 34 --space-md: 1.5rem; 35 --space-lg: 2.5rem; 36 --space-xl: 4rem; 37 38 /* Shadows */ 39 --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); 40 --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1); 41} 42 43html { 44 font-size: 100%; 45 scroll-behavior: smooth; 46} 47 48body { 49 font-family: var(--font-body); 50 font-size: var(--text-base); 51 line-height: 1.6; 52 color: var(--color-text); 53 background: var(--color-bg); 54} 55 56/* ============================================ 57 SKIP LINK 58 ============================================ */ 59.skip-link { 60 position: absolute; 61 top: -50px; 62 left: 0; 63 background: var(--color-primary); 64 color: #fff; 65 padding: var(--space-xs) var(--space-md); 66 z-index: 1000; 67 text-decoration: none; 68 font-weight: 500; 69 border-radius: 0 0 4px 0; 70 transition: top 0.25s ease; 71} 72 73.skip-link:focus { 74 top: 0; 75} 76 77/* ============================================ 78 PAGE LAYOUT — CSS GRID 79 ============================================ */ 80.page-layout { 81 display: grid; 82 grid-template-columns: 1fr; 83 grid-template-rows: auto 1fr auto; 84 grid-template-areas: 85 "header" 86 "main" 87 "footer"; 88 min-height: 100vh; 89 gap: 0; 90} 91 92/* Desktop: sidebar layout */ 93@media (min-width: 900px) { 94 .page-layout { 95 grid-template-columns: 280px 1fr; 96 grid-template-areas: 97 "header header" 98 "sidebar main" 99 "footer footer"; 100 gap: 0 2.5rem; 101 max-width: 1200px; 102 margin: 0 auto; 103 padding: 0 2rem; 104 } 105} 106 107.page-layout > header { 108 grid-area: header; 109} 110 111.page-layout > aside { 112 grid-area: sidebar; 113} 114 115.page-layout > main { 116 grid-area: main; 117} 118 119.page-layout > footer { 120 grid-area: footer; 121} 122 123/* ============================================ 124 HEADER — STICKY + FLEXBOX 125 ============================================ */ 126header { 127 position: sticky; 128 top: 0; 129 z-index: 100; 130 background: var(--color-surface); 131 border-bottom: 1px solid var(--color-border); 132 padding: var(--space-md) 0; 133 box-shadow: var(--shadow-sm); 134} 135 136header .header-inner { 137 max-width: 1200px; 138 margin: 0 auto; 139 padding: 0 1.5rem; 140 display: flex; 141 flex-wrap: wrap; 142 align-items: baseline; 143 gap: var(--space-xs) var(--space-lg); 144} 145 146header h1 { 147 font-size: var(--text-xl); 148 font-weight: 700; 149 line-height: 1.2; 150 margin: 0; 151} 152 153header h1 a { 154 color: var(--color-text); 155 text-decoration: none; 156} 157 158header > .header-inner > p { 159 color: var(--color-text-muted); 160 font-size: var(--text-sm); 161 margin: 0; 162 flex-basis: 100%; 163} 164 165header nav { 166 margin-left: auto; 167} 168 169header nav ul { 170 list-style: none; 171 display: flex; 172 gap: var(--space-lg); 173 padding: 0; 174 margin: 0; 175} 176 177header nav a { 178 color: var(--color-text-muted); 179 text-decoration: none; 180 font-weight: 500; 181 font-size: var(--text-sm); 182 padding: var(--space-xs) 0; 183 border-bottom: 2px solid transparent; 184 transition: all 0.2s ease; 185} 186 187header nav a:hover, 188header nav a[aria-current="page"] { 189 color: var(--color-primary); 190 border-bottom-color: var(--color-primary); 191} 192 193/* ============================================ 194 SIDEBAR — CONTAINER QUERY SETUP 195 ============================================ */ 196aside { 197 container-type: inline-size; 198 container-name: sidebar; 199 padding: var(--space-md) 0; 200} 201 202/* Sidebar widgets */ 203.widget { 204 background: var(--color-surface); 205 border-radius: 12px; 206 padding: var(--space-md); 207 margin-bottom: var(--space-md); 208 box-shadow: var(--shadow-sm); 209 border: 1px solid var(--color-border); 210} 211 212.widget h3 { 213 font-size: var(--text-base); 214 font-weight: 600; 215 margin-bottom: var(--space-sm); 216 padding-bottom: var(--space-xs); 217 border-bottom: 2px solid var(--color-primary); 218} 219 220.widget ul { 221 list-style: none; 222 padding: 0; 223 margin: 0; 224} 225 226.widget li { 227 margin-bottom: var(--space-xs); 228} 229 230.widget a { 231 color: var(--color-text); 232 text-decoration: none; 233 font-size: var(--text-sm); 234 display: flex; 235 align-items: center; 236 gap: 0.5rem; 237 padding: 0.25rem 0; 238 transition: color 0.2s; 239} 240 241.widget a:hover { 242 color: var(--color-primary); 243} 244 245.widget a::before { 246 content: "→"; 247 color: var(--color-primary); 248 font-size: 0.75rem; 249} 250 251/* ============================================ 252 MAIN CONTENT 253 ============================================ */ 254main { 255 padding: var(--space-lg) 0; 256} 257 258/* Section spacing */ 259main > section { 260 margin-bottom: var(--space-lg); 261} 262 263main > section:last-child { 264 margin-bottom: 0; 265} 266 267/* ============================================ 268 TYPOGRAPHY 269 ============================================ */ 270h2 { 271 font-size: var(--text-xl); 272 font-weight: 600; 273 line-height: 1.3; 274 margin: 0 0 var(--space-md); 275} 276 277h2::after { 278 content: ""; 279 display: block; 280 width: 50px; 281 height: 3px; 282 background: var(--color-primary); 283 margin-top: var(--space-xs); 284 border-radius: 2px; 285} 286 287h3 { 288 font-size: var(--text-lg); 289 font-weight: 600; 290 line-height: 1.4; 291 margin: var(--space-md) 0 var(--space-sm); 292} 293 294p { 295 margin-bottom: var(--space-md); 296 max-width: 65ch; 297} 298 299ul, ol { 300 margin-bottom: var(--space-md); 301 padding-left: 1.5rem; 302} 303 304li { 305 margin-bottom: var(--space-xs); 306} 307 308a { 309 color: var(--color-primary); 310 text-decoration: underline; 311 text-decoration-color: transparent; 312 text-underline-offset: 0.15em; 313 transition: text-decoration-color 0.2s; 314} 315 316a:hover { 317 text-decoration-color: var(--color-primary); 318} 319 320/* ============================================ 321 ARTICLE CARDS — GRID + FLEXBOX 322 ============================================ */ 323/* Card grid on homepage */ 324.posts-grid { 325 display: grid; 326 grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); 327 gap: var(--space-md); 328 margin-top: var(--space-md); 329} 330 331article.card { 332 background: var(--color-surface); 333 border-radius: 12px; 334 padding: var(--space-md); 335 border: 1px solid var(--color-border); 336 box-shadow: var(--shadow-sm); 337 transition: transform 0.2s, box-shadow 0.2s; 338 display: flex; 339 flex-direction: column; 340} 341 342article.card:hover { 343 transform: translateY(-3px); 344 box-shadow: var(--shadow-md); 345} 346 347article.card header { 348 position: static; 349 background: none; 350 border: none; 351 box-shadow: none; 352 padding: 0; 353 margin-bottom: var(--space-sm); 354} 355 356article.card header p { 357 font-size: var(--text-xs); 358 color: var(--color-text-muted); 359 margin: var(--space-xs) 0 0; 360} 361 362article.card h3 { 363 margin: 0; 364 font-size: var(--text-base); 365} 366 367article.card h3 a { 368 color: var(--color-text); 369 text-decoration: none; 370} 371 372article.card h3 a:hover { 373 color: var(--color-primary); 374} 375 376article.card > p { 377 color: #555; 378 font-size: var(--text-sm); 379 flex: 1; /* Pushes footer to bottom */ 380 margin-bottom: var(--space-sm); 381} 382 383/* Card footer — Flexbox */ 384.card-footer { 385 display: flex; 386 justify-content: space-between; 387 align-items: center; 388 gap: var(--space-sm); 389 padding-top: var(--space-sm); 390 border-top: 1px solid var(--color-border); 391 margin-top: auto; 392} 393 394.card-footer .read-more { 395 font-size: var(--text-sm); 396 font-weight: 500; 397 text-decoration: none; 398} 399 400.card-footer .read-more::after { 401 content: " →"; 402 transition: margin-left 0.2s; 403} 404 405.card-footer .read-more:hover::after { 406 margin-left: 4px; 407} 408 409.card-footer time { 410 font-size: var(--text-xs); 411 color: var(--color-text-muted); 412} 413 414/* ============================================ 415 ABOUT PAGE 416 ============================================ */ 417.profile-header { 418 display: flex; 419 flex-wrap: wrap; 420 gap: var(--space-lg); 421 align-items: flex-start; 422 margin-bottom: var(--space-lg); 423} 424 425.profile-header img { 426 width: 150px; 427 height: 150px; 428 border-radius: 50%; 429 border: 4px solid var(--color-surface); 430 box-shadow: var(--shadow-md); 431 object-fit: cover; 432} 433 434.profile-header .profile-info { 435 flex: 1; 436 min-width: 250px; 437} 438 439.profile-header .profile-info h2 { 440 margin-bottom: var(--space-sm); 441} 442 443.profile-header .profile-info h2::after { 444 display: none; 445} 446 447/* Skills grid */ 448.skills-grid { 449 display: grid; 450 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 451 gap: var(--space-md); 452 margin: var(--space-md) 0; 453} 454 455.skill-category { 456 background: #f8f9fa; 457 padding: var(--space-md); 458 border-radius: 8px; 459 border-left: 3px solid var(--color-primary); 460} 461 462.skill-category h4 { 463 font-size: var(--text-sm); 464 font-weight: 600; 465 color: var(--color-text-muted); 466 margin-bottom: var(--space-sm); 467 text-transform: uppercase; 468 letter-spacing: 0.05em; 469} 470 471.skill-category ul { 472 list-style: none; 473 padding: 0; 474 margin: 0; 475} 476 477.skill-category li { 478 font-size: var(--text-sm); 479 margin-bottom: var(--space-xs); 480} 481 482/* ============================================ 483 CONTACT FORM — FLEXBOX ROWS 484 ============================================ */ 485.form-row { 486 display: flex; 487 flex-wrap: wrap; 488 gap: var(--space-md); 489 margin-bottom: var(--space-md); 490} 491 492.form-row > div { 493 flex: 1; 494 min-width: 250px; 495 margin-bottom: 0; 496} 497 498form label { 499 display: block; 500 font-size: var(--text-sm); 501 font-weight: 500; 502 margin-bottom: var(--space-xs); 503 color: var(--color-text); 504} 505 506form input[type="text"], 507form input[type="email"], 508form input[type="tel"], 509form select, 510form textarea { 511 width: 100%; 512 padding: 0.75rem 1rem; 513 border: 1px solid var(--color-border); 514 border-radius: 6px; 515 font-family: inherit; 516 font-size: var(--text-base); 517 color: var(--color-text); 518 background: #fafbfc; 519 transition: border-color 0.2s, box-shadow 0.2s; 520} 521 522form input:focus, 523form select:focus, 524form textarea:focus { 525 outline: none; 526 border-color: var(--color-primary); 527 box-shadow: 0 0 0 3px rgba(41, 128, 185, 0.15); 528 background: var(--color-surface); 529} 530 531fieldset { 532 border: 1px solid var(--color-border); 533 border-radius: 8px; 534 padding: var(--space-md); 535 margin-bottom: var(--space-md); 536} 537 538legend { 539 font-weight: 600; 540 padding: 0 var(--space-xs); 541 color: var(--color-text-muted); 542 font-size: var(--text-sm); 543} 544 545/* Checkbox/radio rows */ 546.checkbox-row { 547 display: flex; 548 align-items: center; 549 gap: 0.5rem; 550 margin-bottom: var(--space-xs); 551} 552 553.checkbox-row label { 554 margin: 0; 555 font-weight: 400; 556} 557 558input[type="checkbox"], 559input[type="radio"] { 560 accent-color: var(--color-primary); 561 width: 18px; 562 height: 18px; 563} 564 565/* Buttons */ 566.form-actions { 567 display: flex; 568 gap: var(--space-sm); 569 flex-wrap: wrap; 570} 571 572button[type="submit"] { 573 background: var(--color-primary); 574 color: #fff; 575 border: none; 576 padding: 0.875rem 2rem; 577 font-size: var(--text-base); 578 font-weight: 600; 579 border-radius: 6px; 580 cursor: pointer; 581 transition: background 0.2s, transform 0.1s; 582} 583 584button[type="submit"]:hover { 585 background: var(--color-primary-dark); 586} 587 588button[type="submit"]:active { 589 transform: scale(0.98); 590} 591 592button[type="reset"] { 593 background: transparent; 594 color: var(--color-text-muted); 595 border: 1px solid var(--color-border); 596 padding: 0.875rem 1.5rem; 597 font-size: var(--text-base); 598 border-radius: 6px; 599 cursor: pointer; 600 transition: all 0.2s; 601} 602 603button[type="reset"]:hover { 604 background: #f4f4f5; 605 color: var(--color-text); 606} 607 608/* ============================================ 609 FOOTER 610 ============================================ */ 611footer { 612 text-align: center; 613 padding: var(--space-lg) 0; 614 color: var(--color-text-muted); 615 font-size: var(--text-sm); 616 border-top: 1px solid var(--color-border); 617 margin-top: var(--space-lg); 618} 619 620footer p { 621 margin: 0 auto var(--space-xs); 622} 623 624footer a { 625 color: var(--color-text-muted); 626 text-decoration: none; 627 margin: 0 var(--space-xs); 628} 629 630footer a:hover { 631 color: var(--color-primary); 632} 633 634/* ============================================ 635 CONTAINER QUERIES — SIDEBAR WIDGETS 636 ============================================ */ 637@container sidebar (min-width: 250px) { 638 .widget.links-widget ul { 639 display: grid; 640 grid-template-columns: 1fr 1fr; 641 gap: var(--space-xs); 642 } 643} 644 645@container sidebar (max-width: 249px) { 646 .widget.links-widget a { 647 padding: var(--space-xs) 0; 648 } 649} 650 651/* ============================================ 652 RESPONSIVE ADJUSTMENTS 653 ============================================ */ 654@media (max-width: 899px) { 655 .page-layout { 656 padding: 0 1rem; 657 } 658 659 header .header-inner { 660 flex-direction: column; 661 align-items: flex-start; 662 gap: var(--space-sm); 663 } 664 665 header nav { 666 margin-left: 0; 667 } 668 669 aside { 670 order: 1; /* Move sidebar below main on mobile */ 671 padding: var(--space-md) 0 0; 672 border-top: 1px solid var(--color-border); 673 } 674 675 main { 676 padding: var(--space-md) 0; 677 } 678 679 .profile-header { 680 flex-direction: column; 681 align-items: center; 682 text-align: center; 683 } 684 685 .form-row { 686 flex-direction: column; 687 gap: 0; 688 } 689 690 .form-row > div { 691 min-width: 100%; 692 margin-bottom: var(--space-md); 693 } 694 695 .form-actions { 696 flex-direction: column; 697 } 698 699 button[type="submit"], 700 button[type="reset"] { 701 width: 100%; 702 } 703}
Updated index.html Structure
Your HTML needs a wrapper div for the grid layout:
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>My Personal Blog</title> 7 <link rel="stylesheet" href="styles.css"> 8</head> 9<body> 10 11 <a href="#main-content" class="skip-link">Skip to main content</a> 12 13 <div class="page-layout"> 14 15 <header> 16 <div class="header-inner"> 17 <h1><a href="index.html">My Personal Blog</a></h1> 18 <p>Thoughts on web development, design, and technology</p> 19 <nav> 20 <ul> 21 <li><a href="index.html" aria-current="page">Home</a></li> 22 <li><a href="about.html">About</a></li> 23 <li><a href="contact.html">Contact</a></li> 24 </ul> 25 </nav> 26 </div> 27 </header> 28 29 <aside> 30 <div class="widget links-widget"> 31 <h3>Quick Links</h3> 32 <ul> 33 <li><a href="https://developer.mozilla.org" target="_blank" rel="noopener noreferrer">MDN Docs</a></li> 34 <li><a href="https://caniuse.com" target="_blank" rel="noopener noreferrer">Can I Use</a></li> 35 <li><a href="https://web.dev" target="_blank" rel="noopener noreferrer">Web.dev</a></li> 36 <li><a href="https://css-tricks.com" target="_blank" rel="noopener noreferrer">CSS-Tricks</a></li> 37 </ul> 38 </div> 39 <div class="widget"> 40 <h3>Newsletter</h3> 41 <p style="font-size: var(--text-sm); color: var(--color-text-muted); margin-bottom: var(--space-sm);"> 42 Get the latest tutorials delivered to your inbox. 43 </p> 44 <a href="#" style="font-size: var(--text-sm); font-weight: 500;">Subscribe →</a> 45 </div> 46 </aside> 47 48 <main id="main-content"> 49 <section> 50 <h2>Latest Posts</h2> 51 <div class="posts-grid"> 52 53 <article class="card"> 54 <header> 55 <h3><a href="posts/semantic-html.html">Why Semantic HTML Matters More Than Ever</a></h3> 56 <p><time datetime="2026-08-10">Aug 10, 2026</time> · 8 min read</p> 57 </header> 58 <p>Semantic HTML isn't just about SEO. It's about building a web that works for everyone — including the 1 billion people worldwide with disabilities...</p> 59 <div class="card-footer"> 60 <a href="posts/semantic-html.html" class="read-more">Read article</a> 61 <time datetime="2026-08-10">Aug 10</time> 62 </div> 63 </article> 64 65 <article class="card"> 66 <header> 67 <h3><a href="posts/css-grid-guide.html">CSS Grid: A Visual Guide for Beginners</a></h3> 68 <p><time datetime="2026-08-05">Aug 5, 2026</time> · 12 min read</p> 69 </header> 70 <p>Stop fighting with floats. CSS Grid is the layout system the web has been waiting for since 1996. Here's everything you need to know...</p> 71 <div class="card-footer"> 72 <a href="posts/css-grid-guide.html" class="read-more">Read article</a> 73 <time datetime="2026-08-05">Aug 5</time> 74 </div> 75 </article> 76 77 <article class="card"> 78 <header> 79 <h3><a href="posts/web-accessibility.html">Web Accessibility: Not Optional</a></h3> 80 <p><time datetime="2026-07-28">Jul 28, 2026</time> · 6 min read</p> 81 </header> 82 <p>Accessibility isn't a feature you bolt on at the end. It's a mindset that should guide every decision from the first line of HTML...</p> 83 <div class="card-footer"> 84 <a href="posts/web-accessibility.html" class="read-more">Read article</a> 85 <time datetime="2026-07-28">Jul 28</time> 86 </div> 87 </article> 88 89 </div> 90 </section> 91 </main> 92 93 <footer> 94 <p>© 2026 My Personal Blog. Built with CSS Grid & Flexbox.</p> 95 <p> 96 <a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer">GitHub</a> 97 <a href="https://twitter.com/yourusername" target="_blank" rel="noopener noreferrer">Twitter</a> 98 <a href="https://linkedin.com/in/yourusername" target="_blank" rel="noopener noreferrer">LinkedIn</a> 99 </p> 100 </footer> 101 102 </div> 103 104</body> 105</html>
Part 9: Testing Your Layout
1. The Grid Inspector Test
Open Chrome DevTools → Elements → Layout → Grid. Enable "Show line numbers" and "Show track sizes." Verify your grid areas match your mental model.
2. The Resize Test
Shrink from 1200px to 320px. The layout should:
- Collapse to single column below 900px
- Sidebar moves below main content on mobile
- Cards stack vertically
- Navigation stays horizontal until it must wrap
3. The Sticky Test
Scroll down. The header should stick to the top. Check that z-index: 100 keeps it above content. If something overlaps incorrectly, trace the stacking context.
4. The Container Query Test
Inspect a sidebar widget. Resize the sidebar (not the viewport) using DevTools device mode. The widget should adapt its internal layout based on the sidebar's width, not the screen width.
Key Takeaways
| Concept | What It Means |
|---|---|
| Flexbox | One-dimensional distribution (row OR column) |
| Grid | Two-dimensional architecture (rows AND columns) |
fr | Fractional unit — "share of remaining space" |
minmax() | Sets a size range for grid tracks |
auto-fit / auto-fill | Responsive grids without media queries |
gap | Replaces margin hacks for spacing between items |
position: sticky | Stays relative until scrolled past, then fixed |
| Stacking Context | A 3D layer trap — children can't escape parent's z-index |
| Container Queries | Components respond to their container, not the viewport |
| Logical Properties | Writing-mode-aware spacing (inline, block) |
What's Next?
You now have a fully responsive, grid-based blog with sticky navigation, card layouts, and container-aware sidebar widgets. The structure is solid.
Module 4 covers Modern CSS — the features that replace JavaScript: CSS Custom Properties for theming, scroll-driven animations, the :has() selector, anchor positioning, and CSS Layers. We'll add dark mode, animated page transitions, and interactive components using zero JavaScript.
Save your work. The skeleton is built. Now we make it move.
Quick Reference Cheat Sheet
1/* Flexbox */ 2display: flex; 3justify-content: center | space-between | space-around | flex-start | flex-end; 4align-items: center | stretch | flex-start | flex-end; 5flex-wrap: wrap | nowrap; 6gap: 1rem; 7 8/* Grid */ 9display: grid; 10grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 11grid-template-areas: "header header" "sidebar main" "footer footer"; 12gap: 1rem; 13place-items: center; 14 15/* Positioning */ 16position: relative | absolute | fixed | sticky; 17z-index: 10; /* Only works inside a stacking context */ 18 19/* Container Queries */ 20.container { container-type: inline-size; } 21@container (min-width: 400px) { .child { /* styles */ } } 22 23/* Logical Properties */ 24margin-inline: 1rem; /* left + right (LTR) */ 25margin-block: 2rem; /* top + bottom */ 26padding-inline-start: 1rem; /* "left" in LTR */
Your blog now has professional architecture. In Module 4, we stop writing JavaScript for things CSS can do natively.