CSS intermediate: Flexbox, Grid, and responsive design
If you finished the beginner lesson, you can style individual elements. Good. But how do you put a navbar, a sidebar, and a main content area side by side, and have the whole thing reflow gracefully on a phone? For decades the answer was "use floats and clearfix hacks and pray". Today the answer is Flexbox and Grid. This lesson teaches both, plus responsive design and animation.
Flexbox: the one-dimensional layout system
Flexbox is for arranging items in a single row or a single column. It is the right tool for navbars, button groups, card lists, and any place where you have a row of items that should distribute themselves sensibly.
You turn any element into a flex container with one line:
<code>.toolbar {
display: flex;
gap: 16px; /* space between items, modern and clean */
align-items: center; /* vertical centering, replaces old line-height hacks */
justify-content: space-between; /* push first to left, last to right */
}</code>Inside a flex container, the immediate children become flex items. They flow horizontally by default. You control their behaviour with these properties.
flex-direction: row | column | row-reverse | column-reverse— the main axis.justify-content— how items are distributed along the main axis. Values:flex-start,center,space-between,space-around,space-evenly.align-items— how items are aligned on the cross axis (perpendicular to the main one). Values:stretch,center,flex-start,flex-end,baseline.gap— the space between items. Use this instead of margins on the children.flex-wrap: wrap— allow items to wrap onto a new line when there is not enough room.
A real navbar
<code>.navbar {
display: flex;
align-items: center;
gap: 24px;
padding: 16px 24px;
}
.navbar .logo { margin-right: auto; } /* push everything else to the right */
.navbar a { color: #e9ecf5; }
.navbar .cta { padding: 8px 16px; background: linear-gradient(135deg, #ff8a3d, #ff3d8a); border-radius: 999px; color: #0a0a0a; font-weight: 600; }</code>The trick margin-right: auto on the logo is a classic flexbox pattern. It eats up all the leftover space, pushing everything that follows to the right edge of the container.
Cards that re-flow
<code>.card-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card {
flex: 1 1 280px; /* grow, shrink, ideal width 280px */
padding: 20px;
border: 1px solid #2a2d3a;
border-radius: 14px;
}</code>The flex: 1 1 280px shorthand means "be at least 280px wide, and grow to fill any leftover space if there is room". On a wide screen you get three or four cards per row. On a narrow phone each card takes the full width. No media queries needed for this case.
CSS Grid: the two-dimensional layout system
Grid is for two-dimensional layouts. Rows AND columns at the same time. It is the right tool for page-level layouts, image galleries, dashboards, and any place where alignment on both axes matters.
<code>.page {
display: grid;
grid-template-columns: 240px 1fr; /* sidebar, then flexible main */
grid-template-rows: auto 1fr auto; /* header, main, footer */
min-height: 100vh;
grid-template-areas:
"header header"
"side main"
"footer footer";
}
.page header { grid-area: header; }
.page aside { grid-area: side; }
.page main { grid-area: main; }
.page footer { grid-area: footer; }</code>Named areas are the most readable way to lay out a page in grid. You can see the page structure at a glance. For a one-pager it is hard to beat.
Responsive grid without media queries
The auto-fit and minmax combo is one of the most powerful one-liners in modern CSS.
<code>.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}</code>This creates as many 250px-or-wider columns as fit, and the leftovers are distributed equally. On a 1200px screen with 20px gaps, you get four columns. On a 600px screen, you get two. On a 320px phone, one. No media query needed. The browser does the math for you.
Responsive design: media queries
Media queries let you change styles based on the screen size. The classic pattern is mobile-first: write the mobile styles as the default, then add a media query for larger screens.
<code>/* default = phone */
.card { padding: 12px; font-size: 14px; }
/* tablet and up */
@media (min-width: 720px) {
.card { padding: 20px; font-size: 16px; }
}
/* desktop and up */
@media (min-width: 1024px) {
.card { padding: 32px; font-size: 18px; }
}</code>
There is nothing wrong with desktop-first either, just be consistent. The most common breakpoint values are 480px (small phone), 720px (large phone / small tablet), 1024px (tablet / small laptop), and 1280px (desktop).
Animation that means something
CSS can animate two properties cheaply: transform and opacity. Anything else is fine for one-off flourishes, but if you animate width, height, top, or left on a regular basis the browser will repaint the page on every frame, which is bad for performance and bad for battery life.
<code>.button {
transition: transform 0.2s ease, background 0.2s ease;
}
.button:hover { transform: translateY(-2px); }
.fade-in {
animation: appear 0.5s ease both;
}
@keyframes appear {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}</code>Respect the user's motion preference. Some people get motion sickness from animation. Wrap your animations in a prefers-reduced-motion media query and disable them for those users.
<code>@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}</code>This is a one-time addition to the bottom of your stylesheet that makes the entire site calmer for users who have asked for less motion at the operating system level. It is one of the highest-impact accessibility lines you can write.
CSS custom properties (variables)
Define design tokens once, use them everywhere, change them in one place later.
<code>:root {
--color-bg: #0a0d18;
--color-fg: #e9ecf5;
--color-accent: #ff8a3d;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 32px;
--radius: 14px;
}
body { background: var(--color-bg); color: var(--color-fg); }
.card { padding: var(--space-md); border-radius: var(--radius); }</code>You can also override custom properties inside a media query, which makes theming a breeze. For example, switch the entire colour scheme on a per-page or per-user basis by changing the variables on a wrapper element.
Common intermediate mistakes
Overusing !important. It is tempting to throw !important on a rule when something refuses to work. Resist the urge. Most specificity wars can be solved with better selectors, not heavier hammers. !important should be reserved for utility classes and accessibility overrides.
Animating the wrong properties. We mentioned this above. Stick to transform and opacity for buttery performance. The rest is fine for short transitions but will hurt on long animations.
Hardcoding breakpoints in three places. When you find yourself writing the same breakpoint value in five different files, it is time to define a CSS custom property for it or use a preprocessor variable. Or at least a comment in each file reminding future-you what the breakpoints are.
What is next
You can now build responsive, animated, well-laid-out pages with CSS. The missing piece is behaviour: handling clicks, validating forms, fetching data, and updating the page without a full reload. That is the world of JavaScript, and that is exactly what the next two lessons cover.
← More in CSS