Skip to content

Modern CSS Layout Patterns Every Developer Should Know

Practical CSS layout patterns with Grid, Flexbox, container queries and logical properties that solve real responsive problems without JavaScript.

4 min read
Visual showcase of CSS layout patterns including holy grail, sidebar, masonry, and responsive card grids

CSS layout has changed dramatically. Patterns that required JavaScript libraries, float hacks, or deeply nested divs are now achievable with a few Grid or Flexbox properties. Yet many developers still reach for complex solutions when simpler, more maintainable CSS exists. These patterns solve the layouts you build every week.

The Responsive Sidebar Layout

A sidebar that collapses on narrow screens without media queries. CSS Grid's minmax and auto-fit handle the responsive behavior intrinsically.

csscss
/* ❌ Fixed sidebar with media query breakpoint */
.layout {
  display: flex;
}
.sidebar {
  width: 300px;
  flex-shrink: 0;
}
.main {
  flex: 1;
}
@media (max-width: 768px) {
  .layout {
    flex-direction: column;
  }
  .sidebar {
    width: 100%;
  }
}
/* Problem: arbitrary breakpoint, doesn't respond to container */
csscss
/* ✅ Intrinsic responsive sidebar */
.layout {
  display: grid;
  grid-template-columns: fit-content(300px) minmax(50%, 1fr);
  gap: 1.5rem;
}
/* Sidebar takes content width up to 300px
   Main content gets at least 50% of the container
   When 50% < remaining space, columns stack naturally */
 
/* Even better with container queries */
.layout {
  container-type: inline-size;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}
 
@container (min-inline-size: 700px) {
  .layout {
    grid-template-columns: fit-content(300px) 1fr;
  }
}

The container query version responds to the component's own width instead of the viewport, making it truly reusable regardless of where it's placed.

The Auto-Filling Card Grid

A card grid that automatically adjusts column count based on available space. No media queries, no JavaScript.

csscss
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
  gap: 1.5rem;
}
/* auto-fill creates as many 280px+ columns as fit
   min(280px, 100%) prevents overflow on narrow containers
   Cards stretch equally to fill remaining space */
tstypescript
// The HTML is dead simple
function CardGrid({ items }: { items: CardItem[] }) {
  return (
    <div className="card-grid">
      {items.map(item => (
        <Card key={item.id} {...item} />
      ))}
    </div>
  );
}
 
// Compare to the JavaScript approach:
// ❌ const columns = Math.floor(containerWidth / 280);
// ❌ style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}
// Requires ResizeObserver, re-renders on resize, SSR mismatch

The min() function is the key innovation. Without it, a single card on a narrow screen would try to be 280px wide and overflow. min(280px, 100%) ensures cards never exceed their container.

The Holy Grail Layout

Header, footer, sidebar, and main content area—responsive, with the main content taking remaining vertical space.

csscss
.page {
  min-height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  grid-template-columns: 1fr;
}
 
.header {
  grid-column: 1 / -1;
}
 
.body {
  display: grid;
  grid-template-columns: minmax(200px, 1fr) minmax(0, 3fr);
  gap: 1.5rem;
  padding: 1.5rem;
}
 
.footer {
  grid-column: 1 / -1;
}
 
/* Stack on narrow screens using container query */
.body {
  container-type: inline-size;
}
 
@container (max-inline-size: 600px) {
  .body {
    grid-template-columns: 1fr;
  }
}
csscss
/* The sticky sidebar bonus */
.sidebar {
  position: sticky;
  top: 1rem;
  align-self: start;
  max-height: calc(100dvh - 2rem);
  overflow-y: auto;
}
/* Sidebar scrolls independently and stays visible
   while main content scrolls normally */

Using 100dvh instead of 100vh accounts for mobile browser chrome that changes the viewport height.

The Centered Content with Breakout Elements

Blog-style content with a readable max-width, but some elements (images, code blocks) break out to full width.

csscss
.content {
  --content-width: 65ch;
  --breakout-width: 85ch;
  --full-width: 100%;
 
  display: grid;
  grid-template-columns:
    [full-start]
      minmax(1rem, 1fr)
      [breakout-start]
        minmax(0, calc((var(--breakout-width) - var(--content-width)) / 2))
        [content-start]
          min(var(--content-width), 100% - 2rem)
        [content-end]
        minmax(0, calc((var(--breakout-width) - var(--content-width)) / 2))
      [breakout-end]
      minmax(1rem, 1fr)
    [full-end];
}
 
.content > * {
  grid-column: content;
}
 
.content > .breakout {
  grid-column: breakout;
}
 
.content > .full-width {
  grid-column: full;
}
htmlhtml
<article class="content">
  <h2>Article Title</h2>
  <p>Regular paragraph content stays within the readable width.</p>
 
  <!-- This image breaks out wider -->
  <img class="breakout" src="diagram.webp" alt="Architecture diagram" />
 
  <p>More regular content.</p>
 
  <!-- This spans the full viewport width -->
  <pre class="full-width"><code>// Full-width code block</code></pre>
</article>

This pattern eliminates the need for negative margins or absolute positioning to break elements out of a centered container.

The Flexible Stack with Logical Properties

A vertical stack that adapts to both LTR and RTL layouts automatically using logical properties.

csscss
/* ❌ Physical properties break in RTL */
.stack > * + * {
  margin-top: 1rem;
}
.card {
  padding-left: 1.5rem;
  border-left: 3px solid var(--accent);
  text-align: left;
}
 
/* ✅ Logical properties work in any writing mode */
.stack > * + * {
  margin-block-start: 1rem;
}
.card {
  padding-inline-start: 1.5rem;
  border-inline-start: 3px solid var(--accent);
  text-align: start;
}
csscss
/* A complete token-based spacing system with logical properties */
.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  align-items: center;
}
 
.sidebar-layout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-m);
}
 
.sidebar-layout > :first-child {
  flex-basis: 20rem;
  flex-grow: 1;
}
 
.sidebar-layout > :last-child {
  flex-basis: 0;
  flex-grow: 999;
  min-inline-size: 50%;
}
/* Sidebar wraps below main content when space is tight
   No media queries — responds to available space */

Container Query Components

Container queries let components adapt to their container's size rather than the viewport. This makes components truly portable.

csscss
.product-card {
  container-type: inline-size;
  container-name: product;
}
 
.product-card__layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}
 
/* Horizontal layout when card is wide enough */
@container product (min-inline-size: 400px) {
  .product-card__layout {
    grid-template-columns: 150px 1fr;
  }
 
  .product-card__image {
    aspect-ratio: 1;
    object-fit: cover;
  }
}
 
/* Full featured layout in wide containers */
@container product (min-inline-size: 600px) {
  .product-card__layout {
    grid-template-columns: 200px 1fr auto;
  }
 
  .product-card__actions {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}
tstypescript
// Same component works in any width context
function ProductPage() {
  return (
    <main>
      {/* Card adapts to narrow sidebar */}
      <aside style={{ width: "300px" }}>
        <ProductCard product={featured} />
      </aside>
 
      {/* Same card adapts to wide main area */}
      <section>
        <ProductCard product={featured} />
      </section>
 
      {/* Same card adapts to full-width hero */}
      <div style={{ width: "100%" }}>
        <ProductCard product={featured} />
      </div>
    </main>
  );
}

Key Takeaways

Modern CSS layout is powerful enough to handle responsive behavior intrinsically—without media queries, JavaScript measurements, or framework utilities. Use Grid's auto-fill with minmax for card grids that self-adjust. Use container queries for components that respond to their context rather than the viewport. Adopt logical properties (block-start, inline-end) so layouts work correctly in any writing direction. The best CSS patterns are declarative: you describe what you want, not how to calculate it. Let the browser do the math. Write less, maintain less, and ship layouts that work across every screen size and writing system by default.

Wilfredo Rujel

Wilfredo Rujel

Full Stack Software Engineer

Share this postX