Updated Jul 6, 2026

Fluid Layouts

Media queries fix specific breakpoints, but they're patches on top of a layout that's fundamentally rigid. A fluid layout bends on its own between breakpoints, so you need fewer of them. The tool is simple: stop hardcoding pixel widths, and let the browser do the flexing.

The problem with fixed pixels

Here's the card layout from Phase 1, but built the way a lot of beginners first reach for it - fixed widths instead of flex: 1:

.cards {
  display: flex;
  gap: 16px;
}

.card {
  width: 300px;
  padding: 24px;
  border: 1px solid #ddd;
}

Three cards at 300px plus gaps need at least 964px of space. On a 1440px desktop, fine. On a 375px phone, the cards don't shrink - they overflow, and the whole page grows a horizontal scrollbar. The browser won't compress a fixed width to fit; content spills past the viewport edge instead.

The fix: relative units and wrapping

Swap fixed widths for flexible ones:

.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.card {
  flex: 1 1 280px;
  padding: 24px;
  border: 1px solid #ddd;
}

flex: 1 1 280px means: grow to fill space, shrink if needed, but use 280px as the natural starting size. Combined with flex-wrap: wrap, cards that no longer fit on one row drop to the next row instead of overflowing. At 1440px you get three cards per row; around 900px, two; below 600px, one per row - all without writing a single media query. Grid does the same job with repeat(auto-fit, minmax(280px, 1fr)) on grid-template-columns, which wraps columns automatically as space runs out.

This is the core habit: reach for units and layout modes that respond to available space before reaching for a breakpoint. Media queries still matter for bigger structural changes (Phase 1's stacked-column example), but fluid layout should handle the small adjustments on its own.

Relative units over px

Unit Relative to Good for
% Parent element's size Widths inside a flexible container
rem Root (html) font size Font sizes, spacing - scales if the user changes browser font size
em Current element's font size Spacing that should scale with local text size
vw/vh Viewport width/height Full-bleed sections, fluid type (see below)

A card grid built with padding: 24px ignores a user who bumped their browser's default font size for readability. padding: 1.5rem (24px at the default 16px root size) scales with that setting. Pixels aren't banned - border widths and small fixed details are fine in px - but layout dimensions and type should default to relative units.

clamp() for fluid values without breakpoints

clamp(min, preferred, max) picks a value that scales smoothly between a floor and a ceiling:

h2 {
  font-size: clamp(1.25rem, 4vw, 2rem);
}

Below the width where 4vw equals 1.25rem, the font size stays at the 1.25rem floor. Above the width where 4vw hits 2rem, it caps there. In between, the size scales continuously with viewport width - no jump at a breakpoint, no separate rule for tablet.

Apply the same idea to spacing:

.cards {
  gap: clamp(8px, 2vw, 24px);
}

.card {
  padding: clamp(16px, 3vw, 32px);
}

Gaps and padding shrink on small screens and grow on large ones, continuously, with one line each.

Before and after: the full card layout

Fixed-width version - breaks below ~964px, overflows on phones:

.cards {
  display: flex;
  gap: 16px;
}

.card {
  width: 300px;
  padding: 24px;
  border: 1px solid #ddd;
}

.card h3 {
  font-size: 24px;
}

Fluid version - wraps at any width, spacing and type scale on their own:

.cards {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(8px, 2vw, 24px);
}

.card {
  flex: 1 1 280px;
  padding: clamp(16px, 3vw, 32px);
  border: 1px solid #ddd;
}

.card h3 {
  font-size: clamp(1.1rem, 3vw, 1.5rem);
}

Same three cards, same visual intent, but the second version needs zero media queries to survive the jump from a 320px phone to a 1920px monitor. You'd still add a media query for a structural change - say, hiding a sidebar entirely below 700px - but the day-to-day sizing is handled by the layout itself.

Check your understanding of fluid layout:

[
  {
    "q": "Why does a flex item with `width: 300px` cause a horizontal scrollbar on a narrow phone screen?",
    "choices": [
      "Fixed widths automatically convert to percentages on small screens",
      "The browser refuses to shrink a fixed width below its set value, so content overflows the viewport",
      "Phones ignore the width property entirely",
      "flex-wrap is required for width to work at all"
    ],
    "answer": 1,
    "explain": "A fixed pixel width is a hard floor - the browser won't compress it, so if three of them don't fit, the layout overflows instead of shrinking."
  },
  {
    "q": "What does `clamp(1.25rem, 4vw, 2rem)` do?",
    "choices": [
      "Always renders the font at exactly 4vw",
      "Picks whichever value is smallest",
      "Scales the value with viewport width, but never below 1.25rem or above 2rem",
      "Applies 1.25rem on mobile and 2rem on desktop with no in-between"
    ],
    "answer": 2,
    "explain": "clamp(min, preferred, max) scales continuously between the floor and ceiling, using the preferred value when it falls in range."
  },
  {
    "q": "Which combination lets a row of cards wrap onto a new line automatically as space runs out?",
    "choices": [
      "display: block with fixed widths",
      "flex-wrap: wrap with a flex-basis like flex: 1 1 280px",
      "position: absolute on each card",
      "A media query for every possible screen width"
    ],
    "answer": 1,
    "explain": "flex-wrap: wrap plus a flexible basis lets items reflow onto new rows without any breakpoint-specific CSS."
  }
]

← Phase 1: The Viewport and Media Queries · Guide overview · Phase 3: Responsive Images and Mobile-First Workflow →

Check your understanding 3 questions

1. Why does a flex item with `width: 300px` cause a horizontal scrollbar on a narrow phone screen?

2. What does `clamp(1.25rem, 4vw, 2rem)` do?

3. Which combination lets a row of cards wrap onto a new line automatically as space runs out?