Flexbox, Grid, Container Queries, and Responsive Typography for Tablets, Mobiles, and Foldables
Responsive web design has come a long way since Ethan Marcotte coined the term back in 2010. For years, media queries were the single answer to every layout problem: "The screen is small? Slap a breakpoint on it." But in 2026, that approach is not just outdated — it's limiting.
Modern devices have shattered the old desktop/tablet/mobile trifecta. We now design for foldable phones, ultra-wide monitors, embedded browsers in smart TVs, and everything in between. The good news? CSS has evolved dramatically to meet this challenge. Techniques like CSS Flexbox, CSS Grid, Container Queries, and fluid typography using clamp() give us the tools to build layouts that don't just respond to screen sizes — they adapt intelligently to their own context.
This post is a deep dive into these modern techniques. Whether you're a beginner looking to level up or an intermediate developer trying to shake old habits, you'll walk away with practical, production-ready strategies.
Why Media Queries Alone Fall Short
Before we explore the alternatives, let's be honest about the problem with a media-query-only approach.
The classic pattern looks like this:
.card {
width: 100%;
}
@media (min-width: 768px) {
.card {
width: 50%;
}
}
@media (min-width: 1200px) {
.card {
width: 33.33%;
}
}
This works — until it doesn't. Consider these real-world scenarios:
- A
.cardcomponent is placed in a sidebar on desktop and a full-width feed on mobile. Media queries fire based on the viewport, not the card's parent. Your sidebar card tries to display like a full-width card. - You're building a design system. Components must behave correctly wherever they're dropped in the layout — not just in a fixed template.
- Your users are on foldable devices like the Galaxy Z Fold 6, where the viewport can change drastically mid-session.
Media queries are viewport-aware. But good responsive design needs context-aware components. That's what modern CSS delivers.
1. Flexbox: Intelligent One-Dimensional Layouts
Flexbox has been around since 2012, but many developers still underuse its most powerful features. It excels at one-dimensional layouts — distributing items along a row or a column.
Core Concepts Refreshed
.container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.item {
flex: 1 1 250px; /* grow | shrink | basis */
}
The flex shorthand is your secret weapon here. flex: 1 1 250px means:
- Grow: Fill available space proportionally (
1) - Shrink: Shrink if needed (
1) - Basis: Start at
250pxbefore growing or shrinking
Combined with flex-wrap: wrap, items will naturally reflow into multiple rows when the container is too narrow — no media queries needed.
Real-World Example: A Responsive Card Grid
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.card {
flex: 1 1 min(100%, 300px);
background: var(--surface);
border-radius: 0.75rem;
padding: 1.5rem;
}
Here, min(100%, 300px) ensures each card is never wider than 300px but can shrink to 100% on tiny screens. No @media rule in sight.
Alignment Power
.toolbar {
display: flex;
align-items: center; /* vertical center */
justify-content: space-between; /* horizontal spread */
flex-wrap: wrap;
gap: 0.5rem;
}
flex-wrap here ensures that when toolbar items can't fit on one line, they wrap gracefully instead of overflowing.
When to Use Flexbox
- Navigation bars and toolbars
- Card rows that wrap
- Centering elements vertically and horizontally
- Dynamic lists with varying item widths
2. CSS Grid: True Two-Dimensional Mastery
Where Flexbox handles one dimension, CSS Grid owns two. Rows and columns at the same time — and with modern features like auto-fill, auto-fit, and minmax(), it can create layouts that adapt without a single breakpoint.
The Magic of auto-fill and minmax()
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
Let's break this down:
auto-fill: Create as many columns as possibleminmax(280px, 1fr): Each column is at least280pxwide, but expands to fill available space
On a 1400px screen, you get 4–5 columns. On a 360px mobile, you get 1. The layout is intrinsically responsive.
Auto-Fit vs Auto-Fill
/* auto-fill: preserves empty column tracks */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
/* auto-fit: collapses empty columns, stretches filled ones */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
Use auto-fit when you want items to stretch and fill the full width. Use auto-fill when you want to maintain consistent column widths.
Named Grid Areas: Readable Layouts
.layout {
display: grid;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 260px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100dvh;
}
header { grid-area: header; }
aside { grid-area: sidebar; }
main { grid-area: content; }
footer { grid-area: footer; }
Now restructuring for mobile is clean and explicit:
@media (max-width: 640px) {
.layout {
grid-template-areas:
"header"
"content"
"sidebar"
"footer";
grid-template-columns: 1fr;
}
}
This remains one of the best uses for media queries: macro layout restructuring.
Subgrid: Aligning Nested Elements
CSS Subgrid (now widely supported) lets a child element participate in its parent's grid tracks:
.parent {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.child {
display: grid;
grid-column: span 3;
grid-template-columns: subgrid; /* inherit parent columns */
}
This solves the age-old problem of misaligned nested grids without hardcoding values.
3. Container Queries: The Component-Level Revolution
If there's one CSS feature that changes everything about responsive design, it's Container Queries. Now universally supported across all modern browsers, container queries let components respond to the size of their parent container rather than the viewport.
Setting Up a Query Container
.card-wrapper {
container-type: inline-size;
container-name: card;
}
Now you can write styles that respond to the container:
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
Why This is a Game-Changer
Imagine a <ProductCard> component used in three places:
- A full-width hero on the homepage
- A 3-column grid on a category page
- A narrow sidebar widget
With viewport media queries, you'd have to write separate overrides for every context. With container queries, the component itself knows how to adapt — regardless of where it's placed.
/* The card adapts to its own available width, not the screen */
.product-card {
container-type: inline-size;
}
@container (min-width: 500px) {
.product-card__inner {
flex-direction: row;
align-items: center;
}
.product-card__image {
width: 200px;
flex-shrink: 0;
}
}
Container Query Units
Container queries also introduce new units:
| Unit | Meaning |
|---|---|
cqw | 1% of container's inline size |
cqh | 1% of container's block size |
cqi | 1% of container's inline size (logical) |
cqb | 1% of container's block size (logical) |
.card__title {
font-size: clamp(1rem, 4cqw, 1.75rem);
}
This makes the font scale relative to the card's width — not the screen width. Perfect for design systems.
4. Responsive Typography with clamp() and Fluid Type
Hard-coded font-size values at each breakpoint are a maintenance nightmare. Fluid typography solves this elegantly using clamp().
The clamp() Function
font-size: clamp(minimum, preferred, maximum);
h1 {
font-size: clamp(1.75rem, 4vw + 1rem, 3.5rem);
}
p {
font-size: clamp(1rem, 1.5vw + 0.5rem, 1.25rem);
}
The middle value (4vw + 1rem) is a fluid expression that scales smoothly between the min and max. No breakpoints. No jumps. Just smooth, continuous scaling.
Building a Fluid Type Scale
:root {
--step--1: clamp(0.8rem, 0.8rem + 0.1vw, 0.875rem);
--step-0: clamp(1rem, 1rem + 0.25vw, 1.125rem);
--step-1: clamp(1.25rem, 1.2rem + 0.5vw, 1.5rem);
--step-2: clamp(1.563rem,1.4rem + 0.8vw, 2rem);
--step-3: clamp(1.953rem,1.7rem + 1.2vw, 2.75rem);
--step-4: clamp(2.441rem,2rem + 1.8vw, 3.75rem);
}
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }
body { font-size: var(--step-0); }
This creates a harmonious type scale that's inherently fluid across all screen sizes. Tools like Utopia.fyi can help generate these values.
Fluid Spacing
The same technique applies to spacing:
:root {
--space-s: clamp(0.75rem, 0.5rem + 1vw, 1.25rem);
--space-m: clamp(1.5rem, 1rem + 2vw, 2.5rem);
--space-l: clamp(2rem, 1.5rem + 3vw, 4rem);
}
section {
padding: var(--space-l) var(--space-m);
}
5. Designing for Foldables
The foldable device market is maturing rapidly. Designing for the Galaxy Z Fold, Surface Duo, and other foldables requires awareness of new CSS APIs.
The @media (horizontal-viewport-segments) API
/* Two-panel foldable layouts */
@media (horizontal-viewport-segments: 2) {
.app-layout {
display: grid;
grid-template-columns: env(viewport-segment-width 0 0) 1fr;
}
}
The env() Variables for Foldables
The CSS Environment Variables API provides foldable-specific values:
.pane-left {
width: env(viewport-segment-width 0 0);
height: env(viewport-segment-height 0 0);
}
.pane-right {
width: env(viewport-segment-width 1 0);
}
Practical Foldable Strategy
The best approach for foldables today is to:
- Design mobile-first as your single-pane experience
- Use
@media (min-width: ...)to introduce the two-pane layout - Progressively enhance with foldable-specific APIs
.reading-app {
display: grid;
grid-template-columns: 1fr; /* mobile: single column */
}
@media (min-width: 720px) {
.reading-app {
grid-template-columns: 1fr 1fr; /* fold-open or tablet */
}
}
@media (horizontal-viewport-segments: 2) {
.reading-app {
grid-template-columns:
env(viewport-segment-width 0 0)
env(viewport-segment-width 1 0);
}
}
6. Logical Properties: Writing Mode Independence
If your audience is global, logical properties future-proof your layouts for RTL (right-to-left) languages and vertical writing modes.
/* ❌ Physical (breaks for RTL/vertical) */
.card {
margin-left: 1rem;
padding-right: 1.5rem;
border-left: 3px solid blue;
}
/* ✅ Logical (works everywhere) */
.card {
margin-inline-start: 1rem;
padding-inline-end: 1.5rem;
border-inline-start: 3px solid blue;
}
| Physical | Logical Equivalent |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
padding-top | padding-block-start |
width | inline-size |
height | block-size |
🚀 Pro Tips
- Use
dvhinstead ofvh:100dvhcorrectly accounts for dynamic viewport changes (address bar show/hide on mobile), avoiding the classic100vhoverflow bug on iOS.
.hero {
min-height: 100dvh; /* dynamic viewport height */
}
-
Combine techniques: Use Grid for macro layout, Flexbox for component-level alignment, and Container Queries for fine-grained component behavior. They're complementary, not competing.
-
Prefer
gapover margins:gapworks in both Flexbox and Grid and doesn't create unwanted trailing spacing on the last item. -
Use
aspect-ratiofor consistent media:
.thumbnail {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
-
writing-mode+ Grid = powerful multilingual layouts: Combining logical properties with CSS Grid makes truly internationalized layouts trivial. -
Use CSS
@layerto manage specificity in design systems, preventing utility classes from battling component styles:
@layer base, components, utilities;
- Test with
zoomlevels, not just screen sizes: Many accessibility users zoom to 200%. Fluid typography and flexible layouts handle this gracefully; pixel-fixed layouts don't.
Best Practices
✅ Do
- Start with a mobile-first, single-column layout and layer complexity upward
- Use intrinsic sizing (
minmax,flex,min-content,max-content) before reaching for breakpoints - Apply
container-type: inline-sizeto any component that might be reused in different layout contexts - Use CSS custom properties (variables) for all spacing, typography, and color tokens
- Test on real foldable devices or use Chrome DevTools' device emulation with viewport segments
- Audit your typography with
clamp()to ensure legibility at all sizes
❌ Don't
- Don't hardcode pixel widths on layout containers — use percentages,
frunits, orauto - Don't forget the
gapproperty — it replaces hacky negative margins in modern layouts - Don't use
vhon mobile-facing full-screen sections — always preferdvhorsvh - Don't over-engineer breakpoints — aim for as few as possible, placed where content breaks, not at device widths
- Don't ignore
prefers-reduced-motion— if you add animations to your responsive layouts, wrap them in:
@media (prefers-reduced-motion: no-preference) {
.card {
transition: transform 0.3s ease;
}
}
Common Mistakes
1. Overusing Media Queries for Component-Level Styles
Mistake: Writing viewport-based media queries for a component's internal layout.
Fix: Use container queries. Components don't know the viewport — they know their container.
2. Not Using flex-wrap
Mistake: display: flex without flex-wrap: wrap causes overflow on small screens.
Fix: Always add flex-wrap: wrap unless you intentionally want a single-line flex row (e.g., navigation).
3. Forgetting min-width: 0 on Grid/Flex Children
/* Prevents overflow in flex/grid children with long text */
.grid-item {
min-width: 0;
}
Flex and grid items default to min-width: auto, which can cause overflow with long strings or pre-formatted code.
4. Mixing em and rem Unpredictably
Use rem for font sizes (relative to the root, predictable) and em for spacing that should scale with local context (e.g., button padding). Be intentional about which you use where.
5. Ignoring aspect-ratio for Image Placeholders
Not setting aspect-ratio on images causes layout shifts (CLS) as images load. Always define it:
img {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}
Putting It All Together: A Complete Responsive Component
Here's a <ProfileCard> component that uses every technique we've covered:
/* === Container Setup === */
.profile-card-wrapper {
container-type: inline-size;
container-name: profile;
}
/* === Base (Mobile-First) === */
.profile-card {
display: flex;
flex-direction: column;
gap: var(--space-m);
padding: var(--space-m);
border-radius: 1rem;
background: var(--surface);
}
.profile-card__avatar {
aspect-ratio: 1;
width: clamp(60px, 20cqw, 120px);
border-radius: 50%;
object-fit: cover;
}
.profile-card__name {
font-size: clamp(1.1rem, 3cqw, 1.5rem);
line-height: 1.2;
}
.profile-card__bio {
font-size: clamp(0.875rem, 2.5cqw, 1rem);
color: var(--text-secondary);
}
/* === Container Query: Wider Contexts === */
@container profile (min-width: 360px) {
.profile-card {
flex-direction: row;
align-items: flex-start;
}
.profile-card__avatar {
flex-shrink: 0;
}
}
/* === Full-Bleed Hero Context === */
@container profile (min-width: 600px) {
.profile-card {
padding: var(--space-l);
gap: var(--space-l);
}
.profile-card__name {
font-size: var(--step-3);
}
}
This component works equally well in a sidebar, a card grid, or a hero section — without a single viewport media query.
📌 Key Takeaways
- Flexbox excels at one-dimensional, wrapping layouts. Use
flex: 1 1 min(100%, Xpx)for auto-responsive items. - CSS Grid owns two-dimensional layouts.
auto-fill+minmax()creates intrinsically responsive grids without breakpoints. - Container Queries are the future of component-level responsiveness. Any reusable component should use
container-type: inline-size. clamp()enables fluid, smooth typography and spacing that scales without jumps or breakpoints.- Foldable design uses viewport segment media queries and
env()values — layer them progressively over a solid mobile-first base. - Logical properties (
inline-start,block-end) make layouts work correctly across all writing directions. - Media queries still have their place — particularly for macro layout restructuring, dark mode, reduced motion, and print styles. They just shouldn't be your only tool.
Conclusion
The era of "slap a media query on it" is over. Modern CSS has given us an extraordinarily powerful toolkit for building layouts that are truly adaptive — not just at the viewport level, but at the component level, the typography level, and even the device-form-factor level.
The shift in mindset is the hardest part. Instead of asking "how does this look at 768px?", ask: "how does this component behave when its container is narrow? When the user zooms to 200%? When the viewport unfolds?" Answering those questions with Flexbox, Grid, Container Queries, and clamp() will produce interfaces that feel native on every device — including the ones that don't exist yet.
Start small: pick one component in your current project and add container-type: inline-size. Replace one font-size with a clamp(). Add flex-wrap: wrap and remove a breakpoint. You'll quickly find that the browser is smarter than we've been giving it credit for.
References
- MDN Web Docs — CSS Container Queries
- MDN Web Docs — CSS Grid Layout
- CSS Tricks — A Complete Guide to Flexbox
- Utopia — Fluid Type & Space Scales
- web.dev — New viewport units (dvh, svh, lvh)
- Chrome Developers — Building with Foldable Devices
- Lea Verou — CSS
clamp()Deep Dive - Ahmad Shadeed — Container Queries Land
- W3C CSS Logical Properties Spec
- Can I Use — Container Queries