DNAðŸŽĻ CSSResponsive Design & Fluid Layouts
ðŸĢHatchlingCSSResponsiveContainer Queries

Responsive Design & Fluid Layouts

Responsive design has evolved beyond media queries. Container queries, fluid typography, and intrinsic sizing shift the paradigm from viewport-driven breakpoints to component-driven adaptability.

Responsive Design & Fluid Layouts

Responsive design used to mean "add breakpoints until it looks right on an iPhone." Modern responsive design means components that adapt to their context — not just the viewport — using fluid scales, container queries, and intrinsic sizing.

Media Queries: The Foundation

Breakpoint Strategy

/* Mobile-first: base styles are mobile, layers add for larger screens */
.card {
  padding: 1rem;
  grid-template-columns: 1fr;
}
 
@media (min-width: 48rem) {  /* 768px */
  .card {
    padding: 1.5rem;
    grid-template-columns: 1fr 1fr;
  }
}
 
@media (min-width: 64rem) {  /* 1024px */
  .card {
    padding: 2rem;
    grid-template-columns: 1fr 1fr 1fr;
  }
}

Mobile-first vs desktop-first:

ApproachBase stylesMedia queriesAdvantage
Mobile-firstSmall screenmin-width to add complexityProgressive enhancement, smaller mobile CSS
Desktop-firstLarge screenmax-width to remove featuresLegacy migration, complex desktop layouts

Mobile-first is almost always the right default. Mobile styles are simpler, so they make a better baseline.

Preference Queries

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: oklch(0.15 0 0);
    --color-text: oklch(0.9 0 0);
  }
}
 
/* Reduced motion — critical for accessibility */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
 
/* High contrast */
@media (prefers-contrast: more) {
  :root {
    --color-border: black;
    --color-text: black;
  }
}
 
/* Reduced data — skip non-essential resources */
@media (prefers-reduced-data: reduce) {
  .hero-video { display: none; }
  .hero-image { content: url('/hero-placeholder.svg'); }
}

Container Queries: Component-Level Responsiveness

Media queries respond to the viewport. Container queries respond to the parent container. This is the shift from page-level to component-level responsive design.

/* Define a containment context */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}
 
/* Query the container, not the viewport */
@container card (min-width: 400px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}
 
@container card (min-width: 600px) {
  .card {
    grid-template-columns: 250px 1fr 1fr;
  }
}

Container Types

TypeWhat's containedUse case
inline-sizeWidth only (most common)Cards, sidebar components
sizeWidth and heightAspect-ratio dependent layouts
normalNo containment (default)Not queryable

Container Query Units

.card-title {
  font-size: clamp(1rem, 3cqi, 1.5rem);
  /* cqi = 1% of container's inline size */
}
UnitRelative to
cqwContainer width
cqhContainer height
cqiContainer inline size
cqbContainer block size
cqminSmaller of cqi/cqb
cqmaxLarger of cqi/cqb

Why Container Queries Change Everything

/* Without container queries: sidebar card vs main card */
.card { grid-template-columns: 1fr; }
 
@media (min-width: 768px) {
  .main .card { grid-template-columns: 200px 1fr; }
}
 
@media (min-width: 1024px) {
  .sidebar .card { grid-template-columns: 1fr; }
}

The component must know its context (.main vs .sidebar). With container queries, the card adapts based on its actual available space — no context knowledge needed.

Fluid Typography with clamp()

clamp(min, preferred, max) creates smooth scaling between bounds:

:root {
  --text-sm: clamp(0.875rem, 0.8rem + 0.25vw, 1rem);
  --text-base: clamp(1rem, 0.9rem + 0.35vw, 1.125rem);
  --text-lg: clamp(1.25rem, 1rem + 0.75vw, 1.5rem);
  --text-xl: clamp(1.5rem, 1rem + 1.5vw, 2.25rem);
  --text-2xl: clamp(2rem, 1.25rem + 2.5vw, 3.5rem);
 
  --space-fluid-sm: clamp(0.5rem, 0.3rem + 0.5vw, 0.75rem);
  --space-fluid-md: clamp(1rem, 0.75rem + 1vw, 1.5rem);
  --space-fluid-lg: clamp(1.5rem, 1rem + 2vw, 3rem);
}
 
h1 { font-size: var(--text-2xl); }
p { font-size: var(--text-base); }

The formula clamp(min, preferred, max) with preferred = base + rate × vw creates a linear interpolation between two viewport sizes. The min and max cap the extremes.

Font size
  │
  │         min ───────── preferred slope ───────── max
  │        ╱                                          â•ē
  │───────╱                                            â•ē──────
  │
  └──────────────────────────────────────────────────────── viewport width
       320px                                          1200px

Responsive Images

The <picture> Element

<picture>
  <!-- Art direction: different crop for different sizes -->
  <source media="(min-width: 1024px)" srcset="/hero-wide.webp" type="image/webp">
  <source media="(min-width: 640px)" srcset="/hero-medium.webp" type="image/webp">
  <img src="/hero-small.jpg" alt="Hero" width="640" height="360">
</picture>

srcset and sizes for Resolution Switching

<img
  srcset="
    /photo-400.webp   400w,
    /photo-800.webp   800w,
    /photo-1200.webp 1200w,
    /photo-1600.webp 1600w
  "
  sizes="
    (min-width: 1024px) 50vw,
    (min-width: 640px) 75vw,
    100vw
  "
  src="/photo-800.webp"
  alt="Responsive photo"
  width="800"
  height="600"
  loading="lazy"
  decoding="async"
>

sizes tells the browser how wide the image will be displayed so it can choose the right srcset candidate before downloading. Without sizes, the browser assumes 100vw.

Viewport Units

UnitReferenceBehavior
vh / vwViewport height/widthClassic — but vh fails on mobile
svh / svwSmall viewportExcludes dynamic toolbars (smallest viewport)
lvh / lvwLarge viewportIncludes dynamic toolbars (largest viewport)
dvh / dvwDynamic viewportUpdates as toolbars show/hide
vi / vbViewport inline/blockLogical, respects writing mode
/* The mobile 100vh problem: content hides behind browser chrome */
.hero {
  min-height: 100vh;  /* Broken on mobile — extends behind toolbar */
}
 
/* The fix: dynamic viewport height */
.hero {
  min-height: 100dvh; /* Adjusts as mobile toolbar shows/hides */
}
 
/* Safe fallback pattern */
.hero {
  min-height: 100vh;
  min-height: 100dvh;
}

Logical Properties

Logical properties replace physical directions with flow-relative ones, enabling automatic RTL support:

/* Physical (breaks in RTL) */
.card {
  margin-left: 1rem;
  padding-right: 2rem;
  border-bottom: 1px solid;
  width: 300px;
}
 
/* Logical (works in any writing mode) */
.card {
  margin-inline-start: 1rem;
  padding-inline-end: 2rem;
  border-block-end: 1px solid;
  inline-size: 300px;
}
PhysicalLogicalMaps to (LTR)
widthinline-sizeHorizontal
heightblock-sizeVertical
margin-leftmargin-inline-startLeft
margin-rightmargin-inline-endRight
margin-topmargin-block-startTop
padding-leftpadding-inline-startLeft
border-bottomborder-block-endBottom
topinset-block-startTop

Responsive Component Patterns

Fluid Grid with Intrinsic Breakpoints

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: var(--space-fluid-md);
}

This grid adapts without a single media query: cards are at least 300px wide (or full width if container is < 300px), and the number of columns adjusts automatically.

Responsive Stack

.stack {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-fluid-md);
}
 
.stack > * {
  flex: 1 1 min(100%, 20rem);
}

Items sit side-by-side when there's room, and stack vertically when the container narrows — driven entirely by content, not breakpoints.

Container-Responsive Card

.card-container { container-type: inline-size; }
 
.card {
  display: grid;
  gap: 1rem;
  padding: var(--space-fluid-md);
}
 
@container (min-width: 30rem) {
  .card {
    grid-template-columns: 12rem 1fr;
    grid-template-rows: auto 1fr auto;
  }
 
  .card__image {
    grid-row: 1 / -1;
  }
}

Interview Signal

Senior candidates demonstrate:

  1. Mobile-first reasoning — Explaining why progressive enhancement produces better CSS than desktop-first degradation
  2. Container query mastery — Using container queries for component-level responsive design, understanding containment types
  3. Fluid design — Building layouts with clamp(), intrinsic sizing, and no breakpoints where possible
  4. Image strategy — srcset + sizes for resolution switching, <picture> for art direction, explaining the performance impact
  5. Viewport awareness — Understanding the mobile 100vh problem and when to use dvh vs svh vs lvh