CSS Modern Layouts: What Actually Clicked for Me
Addressing the most common points of confusion regarding CSS Grid, Flexbox, Container Queries, and logical properties.

I've been writing CSS for something like eight years now, and I still sometimes open a layout that someone else built and just stare at it. Not because it's bad. Because CSS layout has gotten genuinely wide. There's Grid, there's Flexbox, there's subgrid now, container queries, logical properties, the has() selector doing layout-adjacent things. And half the stuff I learned in 2019 is still fine, still works, but there's usually a better way now. So this is me trying to lay out where my head's at with modern CSS layout. Not everything, just the parts I actually use and the parts I'm still wrapping my head around.
Flexbox Is Still the Workhorse
I know Grid gets the headlines. Every conference talk, every "modern CSS" tutorial โ Grid is the star. And yeah, Grid is great, I'll get to it. But on actual projects, day to day, I reach for Flexbox probably 70% of the time. Maybe more.
Nav bars. Button groups. A row of cards that just need to sit next to each other. A sidebar layout where one column is fixed and the other fills the rest. Stacking things vertically on mobile. All Flexbox. It's just the right tool for single-axis stuff, and most of the layout problems you run into on a typical page are single-axis problems.
The thing that took me a while to really internalize is that Flexbox is about content-driven sizing. You give items some rules โ grow, shrink, basis โ and the browser distributes space based on what's inside them. That's a different mental model from Grid, which is about defining the structure first and then placing things into it. Neither is better. They solve different problems. But I think most people reach for Grid too early because it feels more "modern," and then they fight with it when Flexbox would've been simpler.
One thing I'll say: if you're still writing float: left for layout purposes in 2026, please stop. I don't mean that harshly. Floats were never meant for layout. They were meant for wrapping text around images. The fact that we used them for page layout for a decade was a collective act of desperation. Flexbox replaced that, and it replaced it years ago.
The Grid Stuff That Actually Matters
Okay, Grid. Here's where I use it.
Full page layouts. Like, the actual skeleton of a page โ header, sidebar, main content, footer. Grid makes that trivially easy:
.page-layout {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
That named-areas syntax is one of those things where once you see it, you can't believe it took CSS this long to get there. You're basically drawing the layout in your code. I show this to junior devs and their eyes light up, because compared to the old ways of doing page layout, this is almost absurdly readable.
The other main place I use Grid is responsive card grids. The auto-fill + minmax() pattern is probably the single most useful CSS trick I've learned in the last five years:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: clamp(1rem, 2vw, 2rem);
}
No media queries. The grid just figures out how many columns fit. When the viewport gets too narrow for two columns, it drops to one. When it's wide enough for four, it uses four. I got this pattern from a CSS-Tricks article ages ago and I've used it on nearly every project since.
But here's the thing โ outside of these two use cases (page skeletons and auto-flowing grids), I don't actually use Grid that much. I know there are people who use it for everything, and that's fine. For me, it's not the default. Flexbox is the default, and Grid is what I pull out when I need two-dimensional control.
Centering: The War Is Over
I feel like I should address this even though it's almost boring at this point. Centering a div used to be a meme. It was genuinely hard for years. The position: absolute plus transform: translate(-50%, -50%) trick. Table cells. Negative margins. All of it was terrible.
Now it's this:
.centered {
display: grid;
place-items: center;
min-height: 100vh;
}
Or with Flexbox:
.centered {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
Both work. I tend to use the Grid version because it's fewer properties, but honestly it doesn't matter. Pick one, move on. The centering problem is solved. Has been for a while. If someone asks you this in an interview in 2026, the correct answer is either of the above, and also maybe a raised eyebrow about why they're still asking this.
The clamp() Thing
This one changed how I write CSS more than almost anything else in recent years. clamp() lets you set a minimum, a preferred value (usually something viewport-relative), and a maximum. The browser picks whatever value falls in that range.
I use it for font sizes:
h1 {
font-size: clamp(1.75rem, 4vw + 0.5rem, 3.5rem);
}
I use it for spacing:
.section {
padding: clamp(2rem, 5vw, 6rem) clamp(1rem, 3vw, 4rem);
}
I use it for widths sometimes, though that's less common.
The thing about clamp() is that it killed a huge number of media queries for me. I used to have breakpoints just for font size changes. Three or four media queries just to make headings scale down on mobile. Now it's one line. The font just smoothly scales between the min and max as the viewport changes. No jumps, no breakpoints, just fluid movement.
There's one gotcha I'll mention. The middle value in clamp() โ the preferred value โ needs to have some viewport-relative unit in it, or the whole thing doesn't really do anything useful. I see people write clamp(1rem, 1.5rem, 2rem) and wonder why it's always 1.5rem. Well, yeah. There's nothing responsive in there. You need vw or vi or something that actually changes with the viewport.
Container Queries Changed My Mind About Components
I was skeptical of container queries for a while. I remember reading the spec discussions back in 2022 or 2023 and thinking "okay, but how often do I really need this?" Turns out: pretty often, once you have them.
The idea is simple. Instead of a component responding to the viewport width (which is what media queries do), it responds to the width of its parent container. So a card component can switch from vertical to horizontal layout based on how much space it's been given, regardless of the screen size.
.card-wrapper {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 500px) {
.card {
display: flex;
flex-direction: row;
gap: 1.5rem;
}
.card-image {
flex: 0 0 200px;
}
}
@container card (max-width: 499px) {
.card {
display: flex;
flex-direction: column;
}
.card-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
}
This matters because reusable components get dropped into all kinds of contexts. A card might live in a three-column grid, or a sidebar, or a full-width hero section. With media queries, you'd have to know in advance what viewport widths correspond to what parent widths, and that breaks down the moment someone rearranges the page layout. Container queries just work, because the component is only looking at its own immediate surroundings.
Browser support crossed 90% back in 2024, so there's really no reason not to use them in production now. The only thing I'll say is that the mental model takes a minute to adjust to. You're used to thinking "when the screen is narrow, do X." Now you have to think "when this component's container is narrow, do X." It's a subtle shift, but it tripped me up at first.
Logical Properties โ The Thing I Keep Forgetting to Use
Alright, I'm going to be honest here. Logical properties are something I know I should use consistently, and I still don't. Not always.
The concept: instead of margin-left, you write margin-inline-start. Instead of padding-top, you write padding-block-start. The "logical" version adapts automatically to the writing direction of the language. So for English (left-to-right), margin-inline-start is the left margin. For Arabic or Hebrew (right-to-left), it flips to the right margin. You don't have to do anything extra.
If you're building something that will ever need to support RTL languages, using logical properties from the start saves you from a really annoying refactor later. I know this. I've even done the annoying refactor on a project that didn't plan ahead. It's not fun.
And yet my fingers still type padding-left half the time. Muscle memory is a real thing. I've started using a stylelint rule to catch it, which helps. But it's an ongoing process.
I think part of the resistance is that the naming is verbose. padding-inline-start is a lot of characters compared to padding-left. And when you're writing it over and over, it feels heavy. But that's a pretty weak excuse, honestly. The shorthand versions help โ padding-inline sets both start and end, margin-block sets both top and bottom (or block-start and block-end, I should say). Those are nice.
For new projects, I'm trying to go all-in on logical properties. For existing codebases, I introduce them gradually. I'm not going to rewrite a whole stylesheet just for this. Life's too short.
The Min-Width: 0 Thing (Or Why Your Flex Items Overflow)
This one bites people constantly. You have a Flexbox layout, everything looks fine, and then someone puts a long URL or an unbreakable string in one of the flex children, and it just shoots right out past the container edge. Horizontal scroll appears. Chaos.
The reason: flex items default to min-width: auto, which means they refuse to shrink below the size of their content. Long unbreakable text counts as content. So the item just... doesn't shrink.
The fix is almost insulting in its simplicity:
.flex-child {
min-width: 0;
}
That's it. That tells the flex item "yes, you're allowed to shrink below your content size." Combine it with overflow: hidden or text-overflow: ellipsis and you're good.
I spent an embarrassing amount of time debugging this on a project once before I figured out what was happening. Now I add min-width: 0 almost reflexively to flex children that contain text. Some people put it on every flex child as a reset. I'm not quite that aggressive about it, but I understand the impulse.
Subgrid: The Missing Piece (That I'm Still Learning)
Subgrid is the newest thing on this list, and I'll be upfront: I haven't used it on a production project yet. I've played with it, I've built demos, but I haven't shipped anything with it. So take this section with that context.
The problem subgrid solves: you have a grid, and inside one of its cells, you have another element that you want to align to the parent grid's tracks. Like a card where the title, description, and footer need to line up across multiple cards in a row, even when the content lengths are different.
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3; /* card takes up 3 rows of the parent */
}
Without subgrid, each card would have its own internal grid, and the rows wouldn't line up between cards. The title in card 1 might be taller than the title in card 2, and the descriptions would start at different heights. Subgrid makes the child participate in the parent's row tracks, so everything lines up.
I think this is going to be really useful once I start using it for real. The alignment problem it solves is one I've hacked around with fixed heights and min-height values for years. But I want to be honest that I haven't yet built the muscle memory for it, and I'm still occasionally surprised by how the row spanning interacts with auto-sized tracks.
Firefox had subgrid for years before Chrome caught up. Chrome shipped it in late 2023, and Safari followed. Support is good now. I just need to actually start using it on real projects instead of just CodePen demos.
What I Actually Do on a New Project
When I start a new project, my CSS layout approach usually looks something like this. I set up a basic page layout with Grid โ the header/sidebar/main/footer skeleton. Everything inside those regions is mostly Flexbox. Card grids and anything that needs two-dimensional auto-flow gets Grid with the auto-fill / minmax() pattern.
I use clamp() everywhere for spacing and typography. I try to use logical properties but sometimes slip up. I add container queries for any component that needs to be reusable across different layout contexts.
And honestly that covers like 95% of layout situations I run into. The remaining 5% is usually some edge case where I need to do something weird with absolute positioning or a sticky element that doesn't behave, and then I spend 45 minutes on Stack Overflow like everyone else.
I don't use CSS frameworks for layout anymore. I used to use Bootstrap's grid system. Before that, Foundation. They were necessary when CSS layout was painful. Now CSS layout is... not painful. The native tools are good enough. Better than good enough, actually. I'd rather write 15 lines of CSS Grid than import a grid framework.
Tailwind is a different conversation. I use Tailwind sometimes, but not because of its layout utilities specifically โ more for the general utility-class workflow. The layout parts of Tailwind are just thin wrappers around the same Flexbox and Grid properties you'd write yourself. Which is fine, but it's not why I reach for Tailwind.
The Stuff I'm Still Not Sure About
I keep seeing people talk about using has() for layout-related things. Like, styling a grid differently based on whether one of its children has a certain class or state. The selector is powerful โ I've used it for form styling โ but for layout specifically, I haven't found a use case that felt better than just using a modifier class or a container query. Maybe I'm not thinking about it right. Maybe there's a pattern I haven't seen yet.
And there's the whole @scope thing. Scoped styles, where you can limit a block of CSS to a specific DOM subtree. I've read the spec. I've read a couple of articles. I get what it does. I don't yet understand when I'd reach for it over just... writing a class name. CSS Modules and scoped styles in frameworks already solve the naming collision problem. So what's @scope giving me on top of that? I genuinely don't know yet. If someone has a compelling example, I'd like to see it.
Anchor positioning is another one. The idea of tethering one element's position to another element, natively in CSS, without JavaScript. For tooltips, popovers, dropdowns. I've seen the demos, they look great. But I haven't tried building anything real with it, and I'm curious how it handles edge cases like viewport boundaries and scrolling containers. The Floating UI library handles all that stuff already, so the CSS version would need to be at least as good for me to switch.
I don't know. CSS keeps moving, and it's moving in directions I mostly like. The layout story is better now than it's ever been. But there's always this feeling of being slightly behind, of knowing there's a technique that would clean up some code I wrote six months ago if I just sat down and learned it properly. I keep meaning to spend a weekend really digging into subgrid and anchor positioning. Haven't gotten around to it yet. Maybe next month. Probably not next month. But eventually.
Written by
Anurag Sinha
Developer who writes about the stuff I actually use day-to-day. If I got something wrong, let me know.
Found this useful?
Share it with someone who might find it helpful too.
Comments
Loading comments...
Related Articles
Tailwind vs CSS Modules: What We Ended Up Doing
How our team debated and resolved the Tailwind vs CSS Modules question. We didn't pick just one.
Accessibility Bugs I Keep Finding in Web Apps
The most frequent accessibility violations I encounter in code reviews, why they matter, and the specific fixes.
What We Learned Migrating to React Server Components
Notes from migrating our SPA to the Next.js App Router and React Server Components. What improved, what broke, and what surprised us.