# Layered CSS Grid + Flexbox Hybrid Layouts

Modern interfaces rarely fit neatly into a single layout primitive. Dashboards juggle dense macro page scaffolding alongside micro component alignment, editorial pages require predictable columns but highly adaptive cards, product views want equal height tiles with fluid internals. Treating Grid and Flexbox as either/or leaves power on the table. The sweet spot is layering them, Grid for macro structure, Flexbox for micro.

## The mental model

Use Grid to answer where things live, the rows, columns, and spatial of your page or section. Go for features like `minmax()`, `auto-fit`, `auto-fill`, `subgrid`, and named areas to make that structure resilient. Use Flexbox inside each grid cell to answer how the content arranges itself, alignment, sizing, wrapping, and fine grained distribution. When you keep these responsibilities separate, you reduce specificity wars, avoid brittle utility cascades, and get more predictable responsive behaviour.

A good tip is to define spacing once at the grid level with `gap` and let components inherit consistent breathing room. Inside components, let Flexbox handle alignment and ordering without affecting the outer rhythm.

## Pattern 1: Grid for page scaffolding, Flexbox for card internals

This is the everyday layout for dashboards and content hubs. Grid defines the overall columns; each card uses Flexbox to arrange its header, media, body, and actions.

```xml
<section class="board">
  <article class="card">
    <header class="card__header">
      <h3>Revenue</h3>
      <span class="badge">Live</span>
    </header>
    <div class="card__body">
      <p>Quarterly revenue up 12%.</p>
      <p class="muted">Updated 3 minutes ago</p>
    </div>
    <footer class="card__footer">
      <button>View</button>
      <button class="secondary">Export</button>
    </footer>
  </article>

  <!-- Repeat .card -->
</section>
```

```css
.board {
  --content-max: 1200px;
  --gap: clamp(12px, 2vw, 24px);

  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--gap);
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--gap);
}

/* Card: Flexbox manages vertical flow and alignment */
.card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem;
  border-radius: 12px;
  background: canvas; /* uses user-agent colour scheme */
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
  min-height: 12rem;
}

.card__header,
.card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.card__body {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  flex: 1; /* stretch to fill, pushes footer down */
}

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  background: color-mix(in oklab, CanvasText 12%, transparent);
}

button {
  padding: 0.5rem 0.8rem;
  border-radius: 8px;
  border: 1px solid color-mix(in oklab, CanvasText 18%, transparent);
  background: canvas;
}

button.secondary { opacity: 0.8 }
.muted { opacity: 0.7 }
```

Grid scales the number of columns automatically, Flexbox ensures the header and footer align neatly while the body flexes to absorb vertical space. The result is consistent gutters, equal height cards per row, and tidy internals without extra wrappers.

## Pattern 2: Masonry-ish responsiveness without JS

True masonry needs either the masonry spec (not widely shipped) or JavaScript. But you can often get good enough with Grid’s dense packing and Flexbox to stabilise card internals.

```css
.gallery {
  display: grid;
  grid-auto-rows: 8px;                  /* base row unit */
  grid-auto-flow: dense;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}

.tile {
  display: flex;                        /* align inner content */
  flex-direction: column;
  justify-content: flex-start;
  border-radius: 10px;
  overflow: clip;
  background: canvas;
  padding: 0.75rem;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
}

/* Height utilities simulated via row spans */
.tile[data-h="1"] { grid-row: span 30; }
.tile[data-h="2"] { grid-row: span 45; }
.tile[data-h="3"] { grid-row: span 60; }
```

```xml
<section class="gallery">
  <article class="tile" data-h="2">
    <img alt="" src="cover-a.jpg" />
    <h4>Explainer</h4>
    <p>How we reduced build times by 48%.</p>
  </article>
  <article class="tile" data-h="1">…</article>
  <article class="tile" data-h="3">…</article>
  <!-- etc. -->
</section>
```

Each tile spans a number of implicit grid rows based on a lightweight data attribute. This gives you a visually varied layout that still respects gutters and column rhythm. Inside each tile, Flexbox handles stacking and spacing of content. It isn’t pixel perfect masonry, but it’s robust, accessible, and requires zero scripts.

## Pattern 3: Subgrid for consistent inner alignment

When a component inside a grid cell needs to align its internal columns with the page grid, `subgrid` shines. Browser support is now strong in Firefox and Chromium, Safari also supports it, making it viable for many projects.

```xml
<section class="spec">
  <header class="spec__head">
    <h2>Technical Specs</h2>
    <p class="lead">All the detail without the noise.</p>
  </header>

  <div class="spec__list">
    <div class="spec__row">
      <div class="label">CPU</div>
      <div class="value">8-core, 16-thread</div>
      <div class="meta">3.8 GHz turbo</div>
    </div>
    <div class="spec__row">
      <div class="label">RAM</div>
      <div class="value">32 GB DDR5</div>
      <div class="meta">2 × 16 GB</div>
    </div>
    <!-- more rows -->
  </div>
</section>
```

```csharp
cssCopyEdit.spec {
  --gap: clamp(12px, 2vw, 20px);
  display: grid;
  grid-template-columns:
    [full-start] minmax(1rem, 1fr)
    [content-start] repeat(12, minmax(0, 5rem))
    [content-end] minmax(1rem, 1fr)
    [full-end];
  gap: var(--gap);
}

.spec__head {
  grid-column: content-start / content-end;
  display: grid;
  gap: 0.5rem;
}

.spec__list {
  grid-column: content-start / content-end;
  display: grid;
  grid-template-columns: subgrid; /*  sync columns with parent */
  grid-column: content-start / content-end;
  gap: var(--gap);
}

/* Child rows also use the same column tracks via subgrid */
.spec__row {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: content-start / content-end;
  align-items: baseline;
  gap: var(--gap);
}

.label { grid-column: 1 / span 3; font-weight: 600; }
.value { grid-column: 4 / span 5; }
.meta  { grid-column: 9 / span 4; opacity: 0.75; }
```

The parent defines a 12 column content grid with fluid gutters, the list and each row inherit those tracks via `subgrid`, guaranteeing precise alignment of labels and values across the whole section. There’s no need for fragile percentage widths or duplicated track calculations.

If you must support older browsers, progressive enhancement is straightforward: replace `grid-template-columns: subgrid` with a reasonable fallback (e.g., two or three manual columns) behind a `@supports not (grid-template-columns: subgrid)` rule.

## Component responsiveness with container queries

Media queries act at the viewport, many components benefit from adapting to the space they’re given instead. Container queries pair beautifully with the grid outside/flex inside approach.

```css
.card {
  container-type: inline-size;
}

/* When a card is wider than 420px, reflow its footer */
@container (min-width: 420px) {
  .card__footer {
    justify-content: flex-end;
    gap: 0.5rem;
  }
}
```

Now cards adapt their internal layout based on their own allocated width, not the global viewport. This avoids awkward breakpoints and keeps components truly modular.

## Sizing that just works

A recurring pain point is elements that either overflow or collapse. A reliable baseline for tiles and cards is:

```css
:root {
  --space: clamp(8px, 1.2vw, 16px);
}

.card {
  min-inline-size: 0;         /* allow flex/grid items to shrink */
  min-block-size: 10rem;
  padding: var(--space);
}

.card h3,
.card p { overflow-wrap: anywhere; }
```

Add `min-inline-size: 0` (or `min-width: 0`) to flex/grid children that must be allowed to shrink, especially next to long text or media.

## Accessibility and keyboard flow

Grid and Flexbox are purely visual, they don’t change DOM order. That’s good for accessibility. Resist the urge to fake layout with `order` unless you also reflect that order in the DOM, otherwise keyboard and screen reader navigation will feel illogical. For components that collapse or expand, combine semantic HTML (e.g., `<button>`, `<section>`, `<h2>`) with CSS state selectors like `:focus-visible` and `:has()` to keep interactivity both stylish and navigable.

```css
button:focus-visible {
  outline: 3px solid color-mix(in oklab, Highlight 50%, transparent);
  outline-offset: 2px;
}
```

Both Grid and Flexbox are fast, but three habits help on complex pages. Prefer `gap` over margins to avoid collapsing surprises and simplify hit testing. Keep `contain` and container queries in mind to limit layout scope on large dashboards. And avoid animating layout affecting properties, animate `transform` and `opacity` and let the layout settle before you start transitions.

## Putting it together: a compact dashboard shell

A final snippet that shows the whole idea in one place, Grid for the page frame, Flexbox for inner composition, container queries for component autonomy.

```xml
<header class="site-head">Acme Analytics</header>
<main class="shell">
  <nav class="nav">…</nav>
  <section class="content board">
    <!-- cards from Pattern 1 -->
  </section>
  <aside class="aside">
    <div class="card">Filters…</div>
    <div class="card">Notes…</div>
  </aside>
</main>
```

```css
.shell {
  --gap: clamp(12px, 2vw, 24px);
  display: grid;
  grid-template-columns: 16rem 1fr minmax(18rem, 22rem);
  grid-template-rows: auto 1fr;
  gap: var(--gap);
  max-width: 1400px;
  margin-inline: auto;
  padding: var(--gap);
}

.site-head { padding: var(--gap); font-weight: 700; }

.nav    { grid-column: 1; }
.content{ grid-column: 2; }
.aside  { grid-column: 3; display: grid; gap: var(--gap); align-content: start; }

/* Collapse aside when space is tight */
@media (max-width: 1100px) {
  .shell {
    grid-template-columns: 14rem 1fr;
  }
  .aside {
    grid-column: 1 / -1;
    grid-auto-flow: column;
    grid-auto-columns: minmax(260px, 1fr);
    overflow-x: auto;
  }
}
```

The shell maintains a three column dashboard on large screens, collapses the aside below, and keeps spacing coherent via a single `gap` token. Each `.card` inside behaves independently thanks to Flexbox and (optionally) container queries.
