Building Magazine Layouts with CSS Grid for Editorial Design

CSS Grid enables magazine-style editorial layouts with grid-template-areas, spanning items, and dense packing. Visual placement separates from source order without restructuring HTML.

The 2014 Hack This Replaces

You have been told that a magazine layout on the web means Bootstrap rows and columns, with col-md-6 and col-lg-4 stacked inside a container. That was the 2014 answer. It failed the moment you wanted a story that spans two columns on desktop but sits full-width on mobile. The HTML had to be restructured for every breakpoint because the visual order was tied to the source order.

CSS Grid separates visual placement from source order using grid-row and grid-column with named lines or grid-template-areas. Here is the modern way: CSS Grid magazine editorial layouts that keep one HTML structure and let the style sheet do the rearranging.

A magazine spread is two-dimensional. A feature story has a hero area, a sidebar, a pull quote, and three smaller stories arranged in a grid. You need simultaneous row and column alignment. Flexbox handles one axis at a time. It wraps but cannot say that item A occupies rows 1 through 3 and columns 1 through 2. Grid's explicit grid tracks, grid-template-columns and grid-template-rows, give you that control. The grid-template-areas property names regions on the grid, and you assign items to those names. Change the area map at a breakpoint, and the items move without touching the HTML.

Feature Story With Named Areas

Here is the first sample: a feature-story layout with a large hero area and two smaller stories. The HTML is a plain article with four children. The CSS names the areas and places each child.

.feature-layout {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-template-rows: auto auto auto;
  grid-template-areas:
    "hero hero"
    "story1 sidebar"
    "story2 sidebar";
  gap: 1rem;
}
.hero { grid-area: hero; }
.story1 { grid-area: story1; }
.story2 { grid-area: story2; }
.sidebar { grid-area: sidebar; }

The hero spans both columns because the area map puts it in both column slots of the first row. The sidebar runs down the right side across two rows. At a narrow viewport, change the map to a single column:

@media (max-width: 40rem) {
  .feature-layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "hero"
      "story1"
      "story2"
      "sidebar";
  }
}

One style rule replaces the entire Bootstrap column shuffle. The source order stays hero, story1, story2, sidebar, and the visual order follows the area map, not the document order.

Grid Template Areas for Editorial Design

Editorial design is about hierarchy. The eye should hit the hero image first, then the lede, then the secondary stories. grid-template-areas makes that hierarchy visible in the style sheet. You read the area map like a miniature layout diagram: "hero hero" tells you the hero spans two columns. The map is a string of space-separated area names, each row a quoted string, and each name appears in the grid-area property of the item that belongs there.

This technique works because the names are arbitrary. You can call the hero lead or feature or main. What matters is that the same name appears in the map and in the item's grid-area value. The map defines the explicit grid, the rows and columns that exist before any item is placed. If you want a gap between stories, the gap property handles it without extra margins.

Multi-Section Layout With Variable Spans

The second sample is a multi-section layout with items spanning variable column and row counts. This is where grid's two-axis power shows. A story can span three columns and two rows, while another spans one column and one row.

.magazine-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto;
  gap: 1rem;
}
.cover-story {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
}
.sidebar-widget {
  grid-column: 4;
  grid-row: 1 / 3;
}
.story-a {
  grid-column: 3;
  grid-row: 1;
}
.story-b {
  grid-column: 3;
  grid-row: 2;
}

Here the cover-story spans columns 1 through 2 and rows 1 through 2. The sidebar-widget sits in column 4 across both rows. Two smaller stories stack in column 3. You can also use the span keyword for a more concise placement:

.cover-story {
  grid-column: span 2;
  grid-row: span 2;
}

The span keyword tells the item to occupy a number of tracks without you counting lines. It is shorthand for the same explicit placement. This is the pattern for a magazine's table of contents or a multi-column feature spread, where each story's size reflects its importance.

Spanning Grid Items in a Magazine Layout

When you span grid items, you are declaring that an item takes more than one track. The span keyword works with both grid-column and grid-row, and it can appear in the grid-area shorthand too. A common mistake is to forget that grid items stretch to fill their grid area by default. The initial value of align-items is stretch. That means a story spanning two rows will grow to fill both, which can cause unexpected vertical expansion if you did not set an explicit height on the container.

Magazine layouts often mix spanning with non-spanning items. You might have a full-width banner at the top, a three-column story block in the middle, and a two-column pull quote below. The grid-auto-flow property controls how items that are not explicitly placed behave. The default is row, which fills the grid row by row. If you set grid-auto-flow: dense, the placement algorithm backfills holes left by larger items.

Dense Packing for Tight Grids

Here is the third sample: a dense-packed grid using grid-auto-flow: dense. This is useful for a gallery or a list of short news briefs where you want no gaps.

.dense-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(6rem, auto);
  grid-auto-flow: dense;
  gap: 0.5rem;
}
.tall-item {
  grid-row: span 2;
}
.wide-item {
  grid-column: span 2;
}

Without dense, a tall item in column 1 leaves a gap in column 2 of the next row. The next item flows to column 1. With dense, the algorithm moves that next item into the gap. The CSS Grid Level 1 specification defines this behaviour: the dense keyword changes the packing order so that items fill holes, even if that means placing an item out of source order.

That source-order violation is the cost. A screen reader traverses the DOM, not the visual grid. If a dense-packed layout places a story visually before another, the reading order remains the source order. For a magazine, where the visual hierarchy matters, test with a screen reader to ensure the narrative order still makes sense. If it does not, skip dense and accept the gaps, or reorder the source.

CSS Grid Print-Style Layout

A print-style layout on the web mimics the constraints of a printed page: fixed column widths, consistent row heights, and no overflow. CSS Grid handles this with grid-template-columns using fixed or minmax() values, and grid-auto-rows to govern the implicit grid. The implicit grid is what the rendering engine creates when items exceed the explicit tracks you declared. If you do not set grid-auto-rows, those extra rows collapse to the content height, which can make a layout look ragged.

For a print feel, set grid-auto-rows to a minimum that keeps the rhythm. The minmax(6rem, auto) pattern gives each extra row at least 6rem, while allowing growth for longer text. Viewport units help too: a hero that is 60vh tall holds a dominant image, and the aspect-ratio property keeps images from jumping the layout before they load. Reserving space with aspect-ratio reduces cumulative layout shift, which matters for a page that feels like a printed spread.

Fixed Columns With a Pull Quote

The fourth sample shows a print-style layout with an aside for a pull quote, using fixed column widths and consistent row gaps.

.print-style {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: minmax(4rem, auto);
  gap: 1rem 1.5rem;
  max-width: 80rem;
  margin-inline: auto;
}
.pull-quote {
  grid-column: 8 / 13;
  grid-row: span 2;
  border-left: 0.25rem solid #333;
  padding-left: 1rem;
}

Here the pull quote sits in the rightmost four columns and spans two rows. The rest of the content flows into the extra grid rows, each at least 4rem tall. The result is a columnar structure with predictable white space, closer to a magazine page than a fluid web layout.

Do not confuse this with the experimental masonry layout. CSS Grid Level 3 defines grid-template-columns: masonry, but it is behind a flag in Firefox only, so it is not usable on the public web. The dense packing technique above is the practical alternative for a masonry-like effect.

Masonry Layout CSS Grid

Masonry layout as a CSS feature is still in the editor's draft of CSS Grid Level 3. The syntax grid-template-columns: masonry exists in a spec, but the only rendering engine that has implemented it is Firefox, and that implementation is behind the layout.css.grid-template-masonry-value.enabled flag. If you need a masonry effect today, the reliable route is a regular grid with grid-auto-flow: dense and row spans, as shown in the previous section. That approach works in every grid-capable engine, because it uses only Level 1 features.

The difference between true masonry and dense grid is the row alignment. True masonry rows are not aligned; each column grows independently. Dense grid keeps all columns on the same row lines, which means items in a row share a height. For a magazine briefs section, dense grid is the better choice because it maintains a clean baseline. For a Pinterest-style image wall, dense grid still beats a JavaScript masonry library, because the rendering engine handles the placement with no script.

One caution: dense packing can violate source-order expectations. A screen reader will announce items in DOM order, not visual order. If your magazine page relies on the visual order to tell a story, test with assistive technology. The CSS Grid Level 1 specification explicitly allows this reordering, so it is not a bug, but it is a responsibility.

Grid Row and Grid Column Placement

The grid-row and grid-column properties are the low-level controls for placing an item. They accept a start line, an end line, or both separated by a slash. Lines are numbered from 1, and negative numbers count from the end. You can also name lines in grid-template-columns or grid-template-rows, then use those names in placement. Named lines make the intent readable: grid-column: sidebar-start / sidebar-end is self-documenting.

The shorthand grid-area combines row start, column start, row end, column end in that order. It also accepts a single name that matches a grid-template-areas region. Using the shorthand with a name is the clearest for a magazine layout, because the area map in the style sheet tells the whole story at a glance.

A common failure is the implicit grid. If you place an item at row 5 but only declared three explicit rows, the rendering engine creates extra rows to hold it. Those extra rows default to auto height, which means the item is as tall as its content. If you expected a fixed height, set grid-auto-rows to a minmax() value. This is the second most common grid mistake after the stretch issue.

Grid Auto Flow Dense and Implicit Grid

The grid-auto-flow property controls how the automatic placement algorithm fills the grid. The default is row, which places items in row order, filling each row before moving to the next. The dense keyword changes this to a greedy backfill: when an item is too large for the remaining space in a row, the algorithm tries to place a later item in that gap before moving on. The result is a tighter layout, but the visual order no longer matches the source order.

This is exactly the behaviour defined in CSS Grid Level 1. The specification says dense packing may cause items to be placed out of order, and it is the author's responsibility to ensure the reading order remains sensible. For a magazine layout, use dense only when the items are homogeneous, like a gallery of cover thumbnails, not for a narrative sequence of stories.

The implicit grid is the set of tracks the rendering engine creates when items exceed the explicit tracks. If your grid-template-columns defines three columns and you place an item at column 5, the engine creates columns 4 and 5 as implicit tracks, sized by grid-auto-columns (default auto). The same applies to rows with grid-auto-rows. Always set these to a minmax() value if you want predictable row heights.

Responsive Column Counts Without Media Queries

You do not need a media query for every breakpoint. The repeat() function with auto-fill or auto-fit and a minmax() value makes the grid adapt to the container width. The difference is subtle: auto-fill creates as many tracks as fit and keeps empty tracks; auto-fit collapses empty tracks so the existing ones stretch. For a magazine's index page, auto-fit is usually right, because you want the stories to fill the row.

Auto-Fit Story List

Here is the fifth sample, a responsive story list that goes from one column on a phone to four on a wide desktop, with no media queries.

.story-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1.5rem;
}

Each story is at least 14rem wide. When the container has space for five, it makes five columns. When it only fits two, it makes two. The 1fr maximum lets the columns grow equally to fill the space. This one declaration replaces the entire float grid system you might have learned in 2014.

Combine this with a fixed grid-template-areas map for the hero section, and you get a hybrid: the hero uses named areas at a wide viewport, while the story list below uses auto-fit to flow freely. The minmax() function is the workhorse here, providing both a minimum size for readability and a flexible maximum.

The Difference Between Auto-Fill and Auto-Fit

The auto-fill and auto-fit keywords differ in how they treat empty tracks. With auto-fill, the grid creates as many tracks as can fit, and any tracks with no items remain as empty space. With auto-fit, those empty tracks are collapsed to zero, and the items stretch across the full width. For a magazine layout, use auto-fit when you want the stories to fill the row edge to edge. Use auto-fill if you want to preserve a consistent column width even when there are fewer items, which can be useful for a placeholder-heavy template.

This is a Level 1 feature, so it is safe to use. The rendering engine computes the number of tracks based on the container width, not the viewport, which means it responds to a sidebar resizing, not just the window. That is the distinction from container queries, which respond to the container's size for other purposes, but here the grid is doing the sizing internally.

Accessibility and Source Order

Grid's power to reorder items visually is also its accessibility risk. A screen reader follows the DOM order, not the grid-template-areas map. If your magazine layout places the sidebar first in the HTML but visually on the right, a screen reader announces the sidebar before the main story. That is a failure for users who rely on assistive technology.

The fix is to keep the source order in the narrative sequence: headline, lede, body, then sidebar. Then use grid-template-areas to move the sidebar to the right without moving it in the DOM. This is the correct application of the technique. When you cannot avoid a reorder, test with a real screen reader and a keyboard. The dense packing mode is the most likely to cause a mismatch, so use it sparingly and only for non-narrative content.

There is no CSS property to change screen reader order. The accessibility tree is built from the DOM, and CSS cannot reorder it. This is a hard constraint, not a bug. Design your HTML for the reading order first, then apply grid for the visual order.

Fallbacks for Older Browsers

Grid is supported in every major rendering engine released after March 2017, but the real-world gap is the small percentage of users on older device-locked software, such as iOS Safari on unsupported devices or Android WebView in apps that do not update. Check caniuse for current support data. For those users, a fallback is straightforward: use a one-column block layout outside an @supports guard, then apply grid inside it.

.feature-layout {
  display: block;
}
.feature-layout > * {
  margin-block: 1rem;
}
@supports (display: grid) {
  .feature-layout {
    display: grid;
    grid-template-areas:
      "hero hero"
      "story1 sidebar"
      "story2 sidebar";
    gap: 1rem;
  }
  .feature-layout > * {
    margin-block: 0;
  }
}

The @supports guard checks for display: grid before applying the grid styles. The fallback uses the same HTML, stacked vertically, which is acceptable for a magazine page on an older engine. Do not use a float-based fallback with grid; the floats will interfere with the grid placement. A block layout is the safest fallback.

For the spanning behaviour, the fallback has no equivalent, but a single column is a readable fallback. The minmax() and auto-fit features are covered by the same guard, so the fallback page remains a clean stack.

FAQ

Does grid work with container queries? Yes, but they answer different questions. Grid sizes tracks based on the container's width, while container queries apply styles when the container crosses a size threshold. Use grid for the layout and a container query for typographic changes like a larger font size in a wide container.

Is grid better than flexbox for magazine layouts? For a two-dimensional layout with rows and columns, yes. Flexbox wraps but cannot align items across rows and columns simultaneously. Grid is the correct choice for a magazine spread. Use flexbox for a single row of items like a nav bar.

Can I use grid with subgrid? Yes, in engines that support CSS Grid Level 2. Subgrid lets a nested grid align its tracks with the parent grid. It is useful for aligning story cards within a section. Check caniuse for support, as older Safari versions have gaps.

What is the common mistake with grid rows? Forgetting that extra rows collapse to content height. Set grid-auto-rows to a minmax() value to keep rows consistent. Also remember items stretch by default, so a spanning item fills its whole area.

The Honest Caveat

Grid is the right tool for magazine layouts, but it is not a silver bullet. The dense packing that fills gaps can hurt accessibility, and the experimental masonry syntax is not production-ready. The real skill is choosing between named areas for the hero and auto-flowing tracks for the rest. You will still write media queries for the largest layout changes, though auto-fit reduces their number. The payoff is that your HTML stays clean and your style sheet expresses the layout as a map. That is the modern way, and it will outlast any framework.