DNA🎨 CSSLayout Systems: Flexbox & Grid
πŸ¦–DinosaurCSSFlexboxGridLayout

Layout Systems: Flexbox & Grid

Normal flow, Flexbox, and Grid aren't competing layout models β€” they're layers of a single system. Knowing when to reach for each one separates architectural layouts from hacked-together ones.

Layout Systems: Flexbox & Grid

CSS layout isn't a single system β€” it's a stack of formatting contexts. Senior engineers choose the right layer for each problem: normal flow for documents, Flexbox for one-dimensional distribution, Grid for two-dimensional placement.

Normal Flow & Block Formatting Context

Normal flow is the default. Block elements stack vertically, inline elements flow horizontally. A Block Formatting Context (BFC) is an isolated layout region β€” floats, margins, and overflows are contained within it.

/* Creating a BFC */
.container {
  display: flow-root;  /* Modern, explicit BFC */
}
 
/* These also create a BFC (as a side effect) */
.overflow  { overflow: hidden; }
.flex      { display: flex; }
.grid      { display: grid; }
.absolute  { position: absolute; }

BFC matters because it prevents margin collapsing across boundaries and contains floats. If two vertical margins touch within the same BFC, they collapse. A new BFC prevents this.

Flexbox: One-Dimensional Layout

Flexbox distributes space along a single axis. The mental model is two axes:

Main Axis (justify-content)
──────────────────────────→
 
β”‚  [  Item 1  ]  [  Item 2  ]  [  Item 3  ]
β”‚
β”‚  Cross Axis (align-items)
↓

The Flex Shorthand

flex is a shorthand for flex-grow, flex-shrink, and flex-basis:

.item {
  flex: 1;          /* flex: 1 1 0%   β€” grow equally from zero base */
  flex: 0 0 200px;  /* fixed 200px, no grow, no shrink */
  flex: 2 1 auto;   /* grow at 2x rate, shrink normally, base = content */
}
flex valuegrowshrinkbasisBehavior
flex: initial01autoDon't grow, can shrink
flex: auto11autoGrow and shrink from content size
flex: none00autoRigid β€” no flex at all
flex: 1110%Ignore content size, share space equally
flex: 2210%Get twice the share of flex: 1 items

Common Flexbox Patterns

/* Centering β€” the classic */
.center {
  display: flex;
  justify-content: center;
  align-items: center;
}
 
/* Sticky footer */
body {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
main { flex: 1; }
 
/* Space between with auto margin */
.nav {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.nav .logo { margin-right: auto; }

The gap Property

gap works in both Flexbox and Grid. It replaces margin-based spacing hacks:

/* Old approach β€” margin on children with negative margin on parent */
.old-list { margin: -8px; }
.old-list > * { margin: 8px; }
 
/* Modern approach */
.new-list { display: flex; gap: 16px; flex-wrap: wrap; }

Grid: Two-Dimensional Layout

Grid controls both rows and columns simultaneously. Think of it as laying items on a coordinate system.

      col 1    col 2    col 3
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
row 1β”‚ header                   β”‚
     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€
row 2β”‚ sidebarβ”‚ main            β”‚
     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€
row 3β”‚ footer                   β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Grid Tracks, Lines, and Areas

.layout {
  display: grid;
  grid-template-columns: 250px 1fr 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header  header  header"
    "sidebar main    main"
    "footer  footer  footer";
  gap: 1rem;
}
 
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

The fr Unit

fr distributes remaining space after fixed tracks are sized:

.grid {
  grid-template-columns: 200px 1fr 2fr;
  /* 200px fixed, then remaining space split 1:2 */
}

minmax() β€” Flexible Bounds

.grid {
  grid-template-columns: minmax(200px, 1fr) 3fr;
  /* First column: at least 200px, at most 1fr of remaining */
 
  grid-template-rows: minmax(100px, auto);
  /* Row: at least 100px, expands to fit content */
}

auto-fill vs auto-fit

Both create responsive grids without media queries, but they differ when there's extra space:

/* auto-fill: keeps empty tracks β€” preserves the grid structure */
.fill {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
 
/* auto-fit: collapses empty tracks β€” items expand to fill */
.fit {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

With 3 items in a 1000px container with 200px minimum:

  • auto-fill: Creates 5 columns (200px each), 3 filled + 2 empty
  • auto-fit: Creates 3 columns that expand to ~333px each

Grid Line Placement

.item {
  grid-column: 1 / 3;      /* From line 1 to line 3 (spans 2 columns) */
  grid-column: 1 / -1;     /* From first to last line (full width) */
  grid-column: span 2;     /* Span 2 columns from auto-placement */
  grid-row: 2 / span 3;    /* Start at row line 2, span 3 rows */
}

Intrinsic Sizing

These keywords let content dictate size rather than fixed values:

.element {
  width: min-content;  /* Shrink as small as possible without overflow */
  width: max-content;  /* Grow to fit content on one line, no wrapping */
  width: fit-content(300px); /* min-content ≀ width ≀ min(max-content, 300px) */
}
KeywordBehaviorUse Case
min-contentSmallest size without overflowTags, badges
max-contentNatural content size, no wrappingTooltips, dropdowns
fit-content(x)Clamps between min-content and xResponsive elements with max width

When to Use Flexbox vs Grid

CriterionFlexboxGrid
DimensionalityOne axis at a timeTwo axes simultaneously
Content vs layoutContent dictates layoutLayout dictates placement
Item sizingItems negotiate sizeTracks define size
AlignmentPer-axis alignmentCell-level placement
Best forNavbars, cards in a row, form controlsPage layouts, dashboards, galleries

Rule of thumb: If you're setting flex-wrap and aligning the wrapped rows matters β€” you probably want Grid.

Positioning and Stacking Context

Position Values

ValueBehaviorCreates Stacking Context
staticNormal flow (default)No
relativeOffset from normal position, stays in flowOnly with z-index
absoluteRemoved from flow, relative to containing blockOnly with z-index
fixedRelative to viewportAlways
stickyHybrid β€” normal flow until scroll thresholdAlways

Containing Block

An absolutely positioned element is placed relative to its containing block β€” the nearest ancestor with position other than static:

.parent {
  position: relative;  /* Establishes containing block */
}
 
.child {
  position: absolute;
  top: 0;
  right: 0;  /* Top-right corner of .parent */
}

Stacking Context and z-index

A stacking context is a 3D layering group. z-index only competes within the same stacking context:

/* These create new stacking contexts */
.a { position: relative; z-index: 1; }
.b { opacity: 0.99; }
.c { transform: translateZ(0); }
.d { isolation: isolate; }
.e { will-change: transform; }
Root stacking context
β”œβ”€β”€ .header (z-index: 100)
β”‚   └── .dropdown (z-index: 999999)  ← trapped inside .header's context
β”œβ”€β”€ .modal-overlay (z-index: 200)    ← beats .dropdown despite lower z-index
β”‚   └── .modal (z-index: 1)

The .dropdown at z-index: 999999 cannot escape its parent's stacking context. This is the #1 z-index debugging trap.

Solution: Use isolation: isolate intentionally to create stacking contexts, and keep z-index values in a token system:

:root {
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-overlay: 300;
  --z-modal: 400;
  --z-toast: 500;
}

sticky Positioning

sticky toggles between relative and fixed based on scroll position:

.table-header {
  position: sticky;
  top: 0;
  background: white;
  z-index: var(--z-sticky);
}

Why sticky fails silently:

  1. An ancestor has overflow: hidden or overflow: auto (creates a scroll container boundary)
  2. No threshold is set (top, bottom, left, or right)
  3. The element's containing block is too short for scrolling to occur

Interview Signal

Senior candidates demonstrate:

  1. Mental model clarity β€” Explaining Flexbox as 1D distribution and Grid as 2D placement, not just syntax
  2. BFC understanding β€” Knowing what creates a BFC and why it matters for margin collapsing and float containment
  3. fr vs percentages β€” Understanding that fr distributes remaining space after fixed tracks, while percentages reference the container
  4. Stacking context mastery β€” Diagnosing z-index bugs by identifying stacking context boundaries
  5. Appropriate tool selection β€” Choosing Grid for 2D layout and Flexbox for 1D content distribution, with clear reasoning