Layout patterns

Solve the layouts every site needs, once, in a way that does not break.

11of 26
≈ 12Min read
10Parts

The wrapper

Almost every page needs one: content centred, capped at a readable width, with a gutter that never disappears on a phone.

one line does all three
.wrap {
  width: min(1100px, 100% - 3rem);
  margin-inline: auto;
}

No media query. Below 1100 + 48 the gutter takes over; above it, the cap does.

Centring

pick by situation
/* one child, both axes */
.centre { display: grid; place-items: center; min-height: 100svh; }

/* a row of things, vertically centred */
.bar { display: flex; align-items: center; gap: 1rem; }

/* horizontally, in normal flow */
.block { max-width: 40rem; margin-inline: auto; }

/* an overlay, when there is no layout context to use */
.over { position: absolute; inset: 0; margin: auto; width: fit-content; height: fit-content; }

The footer sits at the bottom of the window when the page is short, and below the content when it is long. Grid does it with two lines.

header, content, footer
body {
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

The 1fr row absorbs whatever space is left. Nothing needs a height.

two columns that collapse on their own
.layout {
  display: grid;
  gap: 2rem;
  grid-template-columns: minmax(0, 1fr);        /* one column by default */
}

@media (min-width: 60rem) {
  .layout {
    grid-template-columns: 16rem minmax(0, 1fr);
  }
}
Why minmax(0, 1fr) and not 1fr

1fr will not shrink below its content, so one wide table or a long URL pushes the sidebar off the screen. minmax(0, 1fr) removes that floor.

A card deck

Cards themselves are usually a flex column so the button sits at the bottom whatever the text length:

responsive without a breakpoint
.deck {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.25rem;
}
the card
.card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.card .cta {
  margin-top: auto;   /* pushed to the bottom */
}

The media object

an avatar and some text
.media {
  display: flex;
  gap: 1rem;
  align-items: start;
}

.media > img { flex: none; }
.media > .body { min-width: 0; }   /* lets long text truncate instead of overflowing */

Full-bleed inside a wrapper

A common need: the article is narrow, but one image should run the full width of the window. A grid with named gutters does it without breaking the element out of its parent.

the "breakout" grid
.article {
  display: grid;
  grid-template-columns:
    [full-start] minmax(1.5rem, 1fr)
    [content-start] min(60rem, 100% - 3rem) [content-end]
    minmax(1.5rem, 1fr) [full-end];
}

.article > * {
  grid-column: content;
}

.article > .bleed {
  grid-column: full;
}

Aspect ratio

no padding hack any more
.thumb {
  aspect-ratio: 16 / 9;
  width: 100%;
  object-fit: cover;
}

.square { aspect-ratio: 1; }

Equal-height columns and vertical rhythm

.flow > + — the "owl" — sets the gap between children only, so there is never a stray margin at the top or bottom of the container. In a flex or grid container, gap does the same job better.

two useful defaults
/* flex and grid items already stretch to equal heights — that is the default */

/* space between siblings without touching the last one */
.flow > * + * {
  margin-block-start: 1.25rem;
}

Things to stop doing

  • float for layout. It was a workaround for a missing feature; the feature arrived.
  • position: absolute for anything that could be a grid cell. Absolute positioning takes the element out of flow, so nothing around it can respond.
  • Fixed heights on anything containing text. Text grows when translated, when the reader zooms, and when the content changes.
  • width: 100vw on a page that scrolls. It includes the scrollbar and causes a horizontal one.
  • Nested wrappers "just in case". Each one is a box whose width you now have to reason about.

Practice